Compare commits

...

79 Commits

Author SHA1 Message Date
David Given
cbcf457ce3 Merge pull request #182 from davidgiven/usb
Add support for multiple simultaneously connected FluxEngines.
2020-08-10 23:32:16 +02:00
David Given
4855f825e2 Show serial number on device connection, and improve the device listing a bit. 2020-08-10 23:16:04 +02:00
David Given
85bc1637f2 Document the use of multiple FluxEngines. 2020-08-10 23:12:33 +02:00
David Given
73398b83a9 Add support for specifying which FluxEngine you want to use with the --devices
parameter.
2020-08-10 22:36:47 +02:00
David Given
2727e66d40 Allow multiple USB implementations. 2020-08-09 22:33:54 +02:00
David Given
8b6be5a501 Move usb.{cc,h} into its own directory. 2020-08-09 21:14:09 +02:00
David Given
4fee29307c Refactor the USB stuff to allow for multiple USB implementations. 2020-08-08 14:52:11 +01:00
David Given
35f8249c67 Merge pull request #178 from davidgiven/mac
Add support for exporting to DiskCopy 4.2 Mac disk image
2020-08-02 17:42:48 +01:00
David Given
d1467a14b8 Merge. 2020-08-02 18:24:15 +02:00
David Given
3e6b9eb74d Document the .diskcopy file format. 2020-08-02 18:22:30 +02:00
David Given
ce2e8fb4b5 First draft of the DiskCopy 4.2 image writer. 2020-07-30 20:21:49 +01:00
David Given
7eaa75c05d Merge pull request #177 from davidgiven/mac
Make Mac double-sided disks work.
2020-07-29 00:38:28 +02:00
David Given
e86de4483a Fix stupid bug where the mac decoder was looking at the wrong bit for the side
data.
2020-07-28 01:25:50 +02:00
David Given
203a74713f Merge pull request #175 from davidgiven/scp
Make SCP import and export properly handle single-sided images.
2020-06-30 11:19:48 +02:00
David Given
59ed2a6793 Make SCP import and export properly handle single-sided images. 2020-06-26 20:25:56 +02:00
David Given
a03283ce64 Merge pull request #174 from davidgiven/scp
Fix the SCP exporter.
2020-06-26 15:40:41 +02:00
David Given
984cdaeb03 Make nanoseconds_t a double to prevent overflow on very large numbers of
revolutions (I've just seen a flux file with 50).
2020-06-26 14:47:25 +02:00
David Given
a1ed4a9171 Fill out the SCP checksum correctly, and add a --indexed mode which causes data
prior to the first index mark to be discarded and sets the INDEX bit in the
header.
2020-06-26 12:20:49 +02:00
David Given
93caf8e549 Merge pull request #172 from davidgiven/b169
Fix a crash when decoding MX disks if advanceToNextRecord finds no records in a track.
2020-06-25 22:15:00 +02:00
David Given
3841942153 Fix a crash when decoding MX disks if advanceToNextRecord finds no records in a
track.
2020-06-25 21:56:33 +02:00
David Given
5706877b67 Merge pull request #171 from davidgiven/newsampler
Manually merge in jboone's new sampler from #164
2020-06-25 21:16:11 +02:00
David Given
d60900262b Remove the pulse converters from the sampler (the new sampler doesn't require
them). Update firmware.
2020-06-25 21:07:58 +02:00
David Given
54ea34400b Merge in jboone's updated sampler branch. 2020-06-25 21:01:35 +02:00
Jared Boone
db2ab8841a Update Sampler.v, moving clock domain crossing to FIFO interface.
Hopefully, I unscrewed the tab/space and line ending mismatches to minimize the diff.
2020-05-30 21:31:17 -07:00
David Given
adba93ae0a Merge pull request #163 from davidgiven/brother120
Untested Brother 120kB write support.
2020-05-24 01:32:28 +02:00
David Given
98587d04a7 Merge from trunk. 2020-05-24 00:56:17 +02:00
David Given
0051b64648 Merge pull request #167 from vanbogaertetienne/trs80fix
Typo fix in FM_TRS80DAM2_PATTERN
2020-05-24 00:47:12 +02:00
vanbogaertetienne
603009ba15 Typo fix in FM_TRS80DAM2_PATTERN 2020-05-22 20:46:48 +02:00
Jared Boone
adb9809692 Simplify Sampler. 2020-05-20 11:34:29 -07:00
David Given
06eb10d2a0 Merge. 2020-05-16 10:38:17 +02:00
David Given
2244299bd9 Add a bias parameter to allow the entire Brother format to be moved around on
the disk.
2020-05-16 10:37:48 +02:00
David Given
6ca06ecafb Fix several embarassingly stupid bugs in the brother 120kB encoder code path. 2020-05-14 10:41:25 +02:00
David Given
9a5958f80b Prevent OOB when appending no bytes to a fluxmap. 2020-05-14 10:40:49 +02:00
David Given
2b53ac057c Fix some bugs which allow erasing tracks with F_FRAME_WRITE_CMD to work again.
(F_FRAME_ERASE_CMD always worked.)
2020-05-13 23:45:58 +02:00
David Given
5deba8af41 Untested Brother 120kB write support. 2020-05-13 14:49:06 +02:00
David Given
3c54a663b8 Merge pull request #157 from davidgiven/amigacorruption
Fix some issues causing corruption when reading Amiga disks
2020-04-09 00:16:38 +02:00
David Given
1fd65452c4 Typo fix. 2020-04-08 23:37:08 +02:00
David Given
30646ccb07 Fix an Amiga decoder bug where truncated sectors would be considered valid (the
Amiga checksum algorithm is weak and zero bytes don't contribute to the
checksum).
2020-04-08 23:35:55 +02:00
David Given
5be7249a30 Merge pull request #155 from davidgiven/amigawriter
Fix stray bytes at the end of images
2020-04-07 23:13:23 +02:00
David Given
067af18103 When writing images, use the sector size in the spec rather than the actual
data size, to avoid problems with multipart formats like the Amiga.
2020-04-07 23:02:47 +02:00
David Given
8dbd2a72a7 Merge pull request #150 from davidgiven/sixbit
Fix the new sampler and sequencer
2020-04-03 23:54:29 +02:00
David Given
c29e131a3b Convert the IBM format back into a unicorn now I've fixed it. 2020-04-03 23:49:01 +02:00
David Given
a9e30c1e49 Fix an off-by-one error in the sequencer that should have it generating correct
sequences.
2020-04-03 22:58:51 +02:00
David Given
972c8c6b61 Fix off-by-one sampler error, so now the clock rates are right again. 2020-04-03 22:27:33 +02:00
David Given
2007ff7546 Fix merge wibble. 2020-04-03 21:49:26 +02:00
David Given
64694580cd Remember to bump the protocol number after the bytecode change. 2020-04-03 21:46:51 +02:00
David Given
deaab94494 Merge pull request #146 from davidgiven/sixbit
Switch to a simplified encoding with a six-bit timer.
2020-04-03 00:45:21 +02:00
David Given
1509e1f89d Document the new bytecode format. 2020-04-03 00:38:20 +02:00
David Given
29e1ddc2ff Bytecode upgrades always produce the latest version; we don't want to upgrade
1->2->3 (although that specific case is harmless, by accident).
2020-04-03 00:36:55 +02:00
David Given
1fe6434563 Something is wrong with the IBM PC writer, I don't know what. Mark it as a
dinosaur again.
2020-04-03 00:27:12 +02:00
David Given
0367b7e77d Merge from trunk. 2020-04-01 00:06:35 +02:00
David Given
e6da85bf64 Merge pull request #145 from davidgiven/writereport
Add a machine readable read report.
2020-04-01 00:04:54 +02:00
David Given
cd19fcdadd The CSV report now writes records for every sector in the output map, even the
missing ones.
2020-03-31 00:14:23 +02:00
David Given
1954f02cfb Don't reset the device on startup because it confuses Linux. 2020-03-30 22:23:18 +01:00
David Given
39b23200b0 Fix missing flag dependency. 2020-03-29 23:12:12 +02:00
David Given
0644d6d965 Remove some stray tracing (which was causing problems). Fix a potential problem
where sampleclock posedges could be lost in the sequencer.
2020-03-29 23:11:53 +02:00
David Given
a075694d8e Rewrite the sequencer to work with the new six bit bytecode. Fiddle with the
USB stuff a lot in an attempt to resolve the weird packet loss issue.
2020-03-29 15:10:35 +02:00
David Given
b1ea5a9a35 Rework the writer to use a fluxsink rather than just writing stuff directly. 2020-03-29 15:08:45 +02:00
David Given
00087cbb6b Fix a catastrophic DMA setup bug which was causing (probably) every other byte
of data to be mixed up with every other byte... but as every other byte was a
0x80 we never noticed up until now.
2020-03-20 00:06:58 +01:00
David Given
1b48ea20c4 Remove the cruncher. 2020-03-20 00:06:07 +01:00
David Given
3d0f019fc4 Replace the sampler with one using the new simplified bytecode. 2020-03-19 23:39:23 +01:00
David Given
a08bfc183f Display the tick value along with each interval in the histogram. 2020-03-19 22:16:07 +01:00
David Given
c5aef9b051 Annotate inspect to display USB bandwidth. 2020-03-15 13:47:17 +00:00
David Given
fc2655ecd6 Rework the bytecode format to use a much simplified setup: a six-bit timer with
the top two bits reserved for pulse and index state. This is actually smaller,
bandwidth-wise, than the old version, and may be smaller than the crunched
version.
2020-03-14 18:58:43 +00:00
David Given
a737c723d3 Make sure to update before installing packages. 2020-03-14 14:47:04 +00:00
David Given
37aa8b62b0 Add a --write-csv=X option to the reader to dump the sector status map as a
machine-readable file.
2020-03-14 14:35:19 +00:00
David Given
a401173f6d Teach the Amiga decoder how many sectors to expect on each track. 2020-03-09 12:54:29 +00:00
David Given
ce76dc4279 Merge pull request #140 from davidgiven/sectors
Fix a couple of annoying but minor issues
2020-02-28 00:08:11 +01:00
David Given
1025bd857b Don't crashloop if the USB's not connected, as it causes the drives to be
constantly reprobed (which runs the motor).
2020-02-27 22:32:27 +01:00
David Given
025802b2d0 Count required sectors correctly on mac disks. 2020-02-27 22:30:05 +01:00
David Given
adbcb2cd31 Merge pull request #139 from davidgiven/sectors
Add support for required sectors, drive autodetection and fix homing on 8" drives.
2020-02-24 21:55:27 +01:00
David Given
c47a563790 Don't seek to track -1 on homing (it appears to upset 8" drives). Detect which
drives are present, so that if only a single drive is attached then it's always
track 0, regardless of which connector it's on.
2020-02-24 21:47:40 +01:00
David Given
04c09d1a5b Hopefully, fix the problem where ^Cing a job gets the board in a confused
state.
2020-02-21 22:16:20 +01:00
David Given
323da8272a Hopefully add support for giving the reader a set of required sectors, so if
one is missing then we can tell and the track can be reread.
2020-02-21 22:14:44 +01:00
David Given
38700c79fc Merge pull request #137 from davidgiven/docs
Update documentation.
2020-02-20 00:32:19 +01:00
David Given
d504d1890a Remember to document that you need a FDD cable. 2020-02-20 00:30:17 +01:00
David Given
d53e757cfb Rework some of the Brother documentation. 2020-02-20 00:25:55 +01:00
David Given
4983239458 Rework the hardware and software documentation. 2020-02-20 00:17:16 +01:00
David Given
376985828a Add the ditaa pinout table. 2020-02-19 23:49:43 +01:00
94 changed files with 2147 additions and 1696 deletions

View File

@@ -10,7 +10,7 @@ jobs:
with: with:
fetch-depth: 1 fetch-depth: 1
- name: apt - name: apt
run: sudo apt install libusb-1.0-0-dev libsqlite3-dev ninja-build run: sudo apt update && sudo apt install libusb-1.0-0-dev libsqlite3-dev ninja-build
- name: make - name: make
run: make run: make

View File

@@ -1,252 +1,252 @@
:400000000080002011000000D5110000D5110000064A08B5136843F020031360044B1A6803F53F5302331A6001F0F2F8E8460040FA46004010B5054C237833B9044B13B16F :400000000080002011000000A10F0000A10F0000064A08B5136843F020031360044B1A6803F53F5302331A6000F0D8FFE8460040FA46004010B5054C237833B9044B13B1EF
:400040000448AFF300800123237010BD6881FF1F00000000503A0000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF00000000B7 :400040000448AFF300800123237010BD6881FF1F0000000020380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF00000000E9
:400080006C81FF1F503A0000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1F32 :400080006C81FF1F20380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1F64
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080024B012200205A7293 :4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080024B012200205A7293
:4001000002F02CBA8881FF1F10B5C4B2204601F041FA0128FAD110BD08B572B60F4B0F49DA680132DA601A690132C82A08BF00221A615A6918690132A72A08BF00224A6104 :4001000002F012B98881FF1F10B5C4B2204601F027F90128FAD110BD08B572B60F4B0F49DA680132DA601A690132C82A08BF00221A615A6918690132A72A08BF00224A613A
:400140005B69002B0CBF02230023002814BF184643F0010002F06AFF62B608BD8881FF1F10B504460D4B8278997E91421CBF00225A7702689A610279094B1A718378002B93 :400140005B69002B0CBF02230023002814BF184643F0010002F050FE62B608BD8881FF1F38B50446C5B2284602F080F8062002F09DFA44F00200C0B202F078F8062002F055
:4001800014BF0220012002F015FFE07802F00CFF2079BDE8104002F043BF00BF8881FF1FA081FF1F70B5C4B220460E4601F0FAF9314605460246204601F0B6FA204601F086 :4001800095FA284602F072F8BDE83840062002F077BA10B5642402F063F830B90120FFF7DFFF013CF7D1204610BD012010BD70B5C4B2054620460E4601F0D2F8012805D089
:4001C000E9F90128FAD0284670BD000038B50B4CA57F5DB904F11800FFF7C2FF012002F053F94FF47A7002F065FB6577E36823620123A377BDE8384002F09ABB8881FF1FFA :4001C000204601F0EBF92846FFF79EFF204601F0CFF8314605460246204601F08BF9204601F0BEF80028FAD1284670BD38B5044D0024285D013402F009FA402CF9D138BDE9
:4002000038B50446C5B2284602F04AF9062002F067FB44F00200C0B202F042F9062002F05FFB284602F03CF9BDE83840062002F041BB10B5642402F02DF920B10120BDE8EA :40020000A081FF1F08B502F023FC002002F02CFC02F03EFC02F048FC80B208BD10B50446012002F03BF8642002F02AFAFFF7EAFF2080002002F032F8642002F021FAFFF7ED
:400240001040FFF7DDBF0120FFF7DAFF013CF2D1F4E7000038B5044D0024285D013402F0EFFA402CF9D138BDAC81FF1F08B502F009FD002002F012FD02F024FD02F02EFD5E :40024000E1FF608010BD08B502F02EFD002002F037FD02F049FD02F053FD80B208BD10B50446FFF796FF322002F00AFAFFF7EBFF20800020FFF774FF322002F001FAFFF740
:4002800080B208BD10B50446012002F021F9642002F010FBFFF7EAFF2080002002F018F9642002F007FBFFF7E1FF608010BD08B502F014FE002002F01DFE02F02FFE02F0EE :40028000E2FF608010BD0FB400B593B014AB53F8042B402102A8019302F0AAFE02A802F04AF802F054F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF72B
:4002C00039FE80B208BD10B50446FFF7B2FF322002F0F0FAFFF7EBFF20800020FFF790FF322002F0E7FAFFF7E2FF608010BD0FB400B593B014AB53F8042B402102A801938B :4002C00023FF62782146BDE81040042001F0A0B83438000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8583043B10020E3
:4003000002F08EFF02A802F030F902F03AF913B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF7EFFE62782146BDE81040042001F086B9643A000007B5002324 :4003000001F0A8FF002002F03BFD002384F8583010BD00BF8881FF1F38B5104D837895F85B2004469A4204D0FFF7E4FF002385F85E302368C5F859302279094B1A71A3784D
:40034000ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB0000F8B51D4C0646FFF733FF637F03B156B91A48FFF7BEFFFFF75EFF012000236077636602F045 :40034000002B14BF0220012002F01AFDE07802F011FD2079BDE8384002F048BD8881FF1FE181FF1F38B50D4C94F8585065B904F15900FFF7D1FF012001F06CFF4FF47A7031
:40038000AFFA3246616E1548FFF7B1FF114D0027636E9E4216D002F07DF800B16F66636E9E4205DD0020FFF72BFF6B6E013305E005DA0120FFF724FF6B6E013B6B6602F08B :4003800002F07EF984F85E50E3682366012384F85830BDE8384002F0B1B900BF8881FF1FF8B51E4C0646FFF7DDFF94F85E3003B15EB91B48FFF767FFFFF7EBFE0120002353
:4003C000B7FAE5E7322002F075FABDE8F8400448FFF78DBF8881FF1F713A0000783A0000953A00002DE9F04F9BB062B602F00AFB8A49042002F02EFB894802F057F889486B :4003C00084F85E00636602F071F93246616E1548FFF759FF114D0027636E9E4216D001F03FFF00B16F66636E9E4205DD0020FFF7B7FE6B6E013305E005DA0120FFF7B0FE47
:4004000002F0FAFD884802F08BF802F0DBFC02F0ADFB002002F0CEFD02F0A6F80221002001F06EF8814D0321084602F061FAAA462C4602F07DFAAB7F73B12A6AEB689B1AE2 :400400006B6E013B6B6602F079F9E5E7322002F037F9BDE8F8400448FFF735BF8881FF1F4138000048380000653800002DE9F04F99B062B602F0CCF99949042002F0F0F914
:4004400041F28832934207D9002002F01DF8002002F0B0FD0023AB7701F08AF818B97448FFF745FF04E001F089F80028F7D109E001F07EF80028FBD06E48FFF738FF0320E6 :40044000984801F019FF984802F0BCFC974801F04DFF02F09DFB02F06FFA002002F090FC01F068FF0221002000F030FF904C012001F0A8F8002384F85B30FFF773FFFFF79A
:4004800001F0A6F9032001F085F80128D1D16A48FFF766FE69490320FFF784FE94F868106748FFF724FF94F86830023B142B00F26A84DFE813F0150068041E0068042400B2 :4004800088FE84F86800FFF735FF012384F85B30FFF768FFFFF77DFE84F86900FFF72AFF844B94F86800844994F869202546002A14BF0A461A46002808BF19467F48FFF7E3
:4004C0006804480068046B006804D1006804F9016804A9036804C9036804CF036804DA0303238DF828308DF829300C238DF82A30B7E394F86A00FFF731FF524BAEE3FFF72E :4004C000E2FE0321084602F0F9F8264602F016F994F8583043B12A6EEB689B1A41F28832934201D9FFF706FF00F028FF18B97448FFF7C9FE04E000F027FF0028F7D10BE0E5
:4005000065FE00236372E068627A02F0FF0132B9EB681B1AB3F57A7FF6DD0B460AE04BB100228AF80920DAF80C10627A12B9EB685B1AFAE707228DF8282004228DF8292037 :4005000000F01CFF10B902F0F9F8F9E76D48FFF7BAFE032001F042F8032000F021FF0128D4D16948FFF7F8FE68490320FFF73FFE94F86A106648FFF7A6FE94F86A30023B1D
:40054000ADF82A308DE30220FFF7DEFD00270DF1280902F0EDF94FF480780026C8EB07039A1906F809200136402EF9D10220FFF7CBFD32464946022001F064F8B8F101087C :40054000142B00F2AB83DFE813F01500A9031E00A9032400A9034600A9036C00A903CF00A903AA01A903E202A9030103A9030803A903220303238DF820308DF821300E23FD
:40058000EBD10137402FE4D12F4B42E04FF00109002702F0CDF94FF00008012001F018F9012000F0F7FF0128FAD10DF1280B4022594601F0B9F8012000F0ECFF0028FAD137 :400580008DF82230FFE294F86C00FFF709FF514BF6E2FFF7E7FE00236372E068627A02F0FF0132B9EB681B1AB3F57A7FF6DD0B4608E03BB100227272F168627A12B9EB68CB
:4005C000064608EB07030593059B1BF806203344DBB2934209D08DE80C003946334642461A48FFF784FE4FF000090136402EEBD108F10108B8F5807FCFD10137402FC8D17D :4005C0005B1AFAE707228DF8202004228DF82120ADF82230D7E20220FFF796FD4FF000080DF1200A02F08AF84FF480790027C9EB0803DA1907F80A200137402FF9D10220BC
:4006000049461348FFF773FEB9F1000F00F05381104B1B8809A8ADF8243023E319010000F900000091000000C50000008881FF1FA33A0000B63A0000A081FF1FF081FF1F13 :40060000FFF782FD3A465146022000F001FFB9F10109EBD108F10108B8F1400FE2D12E4B38E04FF0010A4FF000080DF1200B02F065F84FF0000959460120FFF7B8FD08EBBF
:40064000C03A0000543A0000563A0000CF3A0000EB3A0000583A000094F86A0001F038FF606EFFF77BFE02F047FD944B944F1A78002602F0FB021A701A7842F001021A70CD :40064000090300270493049B1BF807203B44DBB2934209D08DE80C0041463B464A461F48FFF711FE4FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400F92
:400680001A7802F0FE021A701A7802F0FE021A7002F036FD0220FFF737FD41F6FF734FF480420121022002F08BFC84F8A80002F0ADF8B8550136402EF9D1DFF82482002668 :40068000D5D151461648FFF7FEFDBAF1000F00F00E81144B1B8807A8ADF81C3074E200BF19010000F900000091000000C50000008881FF1F77380000733800007A38000022
:4006C00008F18D0B1FFA8BF70136402E14BF3246002218F8010F2244062392F82420402102F0C8F83A4646F2434198F8000002F0D3F84037402EBFB2E6D19AF86B3043B175 :4006C00092380000A5380000E181FF1FF281FF1FAF3800002438000026380000BE380000DA3800002838000094F86C0001F0D6FD606EFFF755FE02F0E7FBB24BDFF8E0825C
:4007000000238AF80930637A002BFCD000238AF80930182200210AA802F04EFD694B4FF0FF320C9340230D930023236062602372236894F8A800234493F8241002F01EF82F :400700001A78002702F0FB021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F0D5FB0220FFF7EEFC012141F6FF734FF48042084602F028FB84F8AA00FD
:4007400094F8A80001F0DCFF012194F8A80001F0AFFF2368002BFCD000239946CAF80430DAF80C200127059202F0E2F8059AE3689B1AB4F86C2093420ED36FB1042195F822 :4007400001F04AFF08F807000137402FF8D1DFF894A200270AF199091FFA89F80137402F14BF3A4600221AF8010F22440623127E402101F065FF424646F24A519AF800005D
:40078000A80002F007F894F8A80002F013F80028F9D107468AF80800237A03F0FF08002B48D16A682B689A4202D1002FDCD141E063680AA80BEB831343440A93C8F1400391 :4007800001F070FF08F14008402F1FFA88F8E5D196F86D3033B100237372637A002BFCD00023737200234FF0FF32236062602372236894F8AA002344197E01F0C5FE94F87B
:4007C0000B9300F0F9FA0B9B09F10109C3F1400398440D9B5FFA88F8E3B93B4E0220FFF793FCA6F1400EBEE80F000FC6BEE80F000FC6BEE80F000FC69EE80F0086E80F0019 :4007C000AA0001F083FE012194F8AA0001F056FE2368002BFCD0002398467360D6F80CA0012701F08BFFE368B4F86E20CAEB030393420DD367B1042195F8AA0001F0B0FEC5
:40080000A6F130014022022000F01CFF703E40230C960D93B8F13F0FCAD962682B4B01321340002BBEBF03F1FF3363F03F030133636099E70AA8267A00F014FB0220FFF76B :4008000094F8AA0001F0BCFE0028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF770FC6968402209EB8111022000F0EDFD6A68634B01321340002B4F
:4008400063FC0D9BF6B2402B07D0022040221E4900F0F8FE0220FFF757FC0D9B022033F040021DBFC3F1400292B21749114600F0E9FE0220FFF748FCFFF7ECFC36B11448FB :40084000BEBF03F1FF3363F03F03013308F101086360C6E70220277AFFF756FC00221146022000F0D5FDFFB20220FFF74DFCFFF7BDFC37B15548FFF706FD0220FFF72AFDB4
:40088000FFF735FD0220FFF759FD06E0114B09A81B88ADF82430FFF73FFD627A4946237A0D48FFF724FD71E20C48FFF720FDE66E16F03F0616D0032066E200BF93640040CD :4008800006E0534B08A81B88ADF82030FFF710FD627A4146237A4F48FFF7F5FC01E24E48FFF7F1FCD4F86E7017F03F0701D00320F5E1012001F0F8FC95F86C0001F0EEFCF4
:4008C000AC81FF1F3892FF1F7892FF1F3F000080F53A00005A3A00000F3B0000223B0000AB81FF1F012001F0F9FD95F86A0001F0EFFD02F001FCBA4BB94A1B78B94F43F089 :4008C00002F002FB454BDFF818811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F0F1FA686EFFF756FD01214FF4804341F6FF7208462E
:4009000004031370137843F001031370137803F0FE031370137803F0FE03137002F0F0FB686EFFF71BFD01214FF4804341F6FF72084601F0D9FD85F8A80001F067FFB855B5 :4009000001F0D8FC85F8AA0001F066FE08F807000137402FF8D1DFF8CC90002709F199031FFA83F804930137402F14BF3A46002219F8010F22440523127E402101F080FEB9
:400940000136402EF9D1DFF8BC82002708F18D039EB207930137402F14BF3A46002218F8010F2244052392F82420402101F082FF314646F2475298F8000001F08DFF403623 :40094000414646F24C4299F8000001F08BFE08F14008402F1FFA88F8E5D100274FF0FF33376098467360BB463B46D6F86E9037725FEA99190CBF4FF0010A4FF0000A216867
:40098000402FB6B2E6D1DAF86C3000269B09182231464FF0FF3B0AA8CAF800600593CAF804B0B1468AF80860B04602F005FC0D973746012000F00CFF069601F0B9FFB8F170 :40098000114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426BD02BB1227A002A76D16A7A002A73D12068049A059302EB8010BAF1000F16D040223F2102F0C9
:4009C000000F0AD14EB9012000F0E4FD012804D14022854900F0A8FE06462268834B01321340002BBCBF03F1FF3363F03F036168B8BF01338B4200F09180069B3BB1237A59 :4009C000E3FA1CE09A6500403F000080E43800002A380000FE380000113900009C640040A081FF1F9F81FF1F014601370120FFF7DEFBC7EB0903D3F1000A4AEB030A21689F
:400A0000002B40F09C806B7A002B40F098800B9BBBBBB8F1000F09D0754A7F2199540133402BFBD1724A0B930A922AE04EB3012000F0B0FD28BBDFF8B8E13F2E0EF1400CB9 :400A0000AB4A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322AD12A683F2A27D14FF00008C5F8048001F070FC85F808806B6895F8AA002B44197E01F0BC
:400A4000BCE80F00AEE80F00BCE80F00AEE80F00BCE80F00AEE80F009CE80F008EE80F00AEF130030A9307F101070B9607DD059BBB4204D0012000F0ABFE464601E04FF08B :400A400083FD95F8AA0001F041FD012195F8AA0001F014FD85F80980637A002BFCD04FF00008012086F8098001F05EFC404601F01BFC00E023B1237A5BB96B7A4BB90123A1
:400A800001080B9BDBB12368079A0AA802EB83120D9BC3F1400313440C9300F0F2F90D9B6BB92A68514B01321340002BBEBF03F1FF3363F03F030133236040230D936368C3 :400A8000626842453FF47BAF0BF1010BD5F8048075E701F043FC012001F006FC002001F043FC042194F8AA0001F05AFD94F8AA0001F066FD0028F9D196F8AA0001F0F4FC07
:400AC00001332BD12B683F2B28D14FF0000BC5F804B001F037FD85F808B06B6895F8A8002B4493F8241001F049FE95F8A80001F007FE012195F8A80001F0DAFD85F809B0CD :400AC000737A327A0293012303920193CDF800A05B463A4649467748FFF7D5FBBAF1000F08D0FFF783FB237A63B17348FFF7CBFB0220D4E0B945F4D070490120FFF757FBF9
:400B0000637A002BFCD04FF0000B01208AF809B001F024FD584601F0E1FC01E0069B2BB1237A63B96B7A53B90123069363685B453FF443AF09F10109D5F804B03DE76168F6 :400B00000137F7E76E48FFF7BEFB6E4B38E094F86C0001F0C3FB606EFFF742FC6A48FFF7B2FB00236372637A002BFCD0012001F0FBFB00237372637A002BFCD0002001F0AA
:400B40002C482268FFF7D3FB01F002FD012001F0C5FC002001F002FD042194F8A80001F019FE94F8A80001F025FE0028F9D19AF8A80001F0B3FD9AF809309AF80820029350 :400B4000F3FB6248FFF79FFB614B19E0002084F85E00FFF725FC5F4B12E094F8683023B195F869200AB985F86C2094F869201AB113B9012385F86C305748FFF7CDFB574B39
:400B8000012303920193CDF800804B463A4605991948FFF7ACFBB8F1000F16D1059BBB420AD0012000F0F6FC01280646F6D10E49FFF7F8FA3F2803DC012000F01DFE04E00C :400B80001B88ADF8203008A8FFF792FB89E0FFF7B1FB02F07BF8002002F01EF82A2701F049FF002001F0ECFE3A46002108A802F0EBF917238DF820308DF8217001F09EFD75
:400BC000304600F005FE0137E8E7FFF743FB0B48FFF78DFB237A0BB10220D5E0084B18E597650040AC81FF1F7892FF1F3F0000803892FF1F2C3B0000373B0000673B00002F :400BC000002001F047FB002002F0DAF8C82001F057FD0DF12200FFF721FB0DF13600FFF73EFB01F08BFD012002F0CAF8322001F047FD0DF12600FFF711FB0DF13A00FFF7D3
:400C00005C3A0000AB81FF1F94F86A0001F060FC606EFFF7A3FB6148FFF769FB00236372637A002BFCD0012001F098FC00238AF80930637A002BFCD0002001F08FFC5848CC :400C00002EFB012001F026FB4FF4967001F038FD01F074FD0DF12E00FFF700FB0DF14200FFF71DFB002001F015FB4FF4967001F027FD01F063FD022002F0A2F8322001F05B
:400C4000FFF755FB574B09E000206077FFF786FB554B03E05548FFF783FA554B1B88ADF828300AA8FFF758FB90E0A37F3BB1002001F00AFC002002F09DF90023AB7702F0F3 :400C40001FFD0DEB0700FFF7E9FA0DF13E00FFF706FB012001F0FEFA4FF4967001F010FD01F04CFD0DF13200FFF7D8FA0DF14600FFF7F5FA002001F0EDFA4FF4967001F06F
:400C80001FF9002002F0C2F82A2601F0EDFF002001F090FF324600210AA802F08DFA17238DF828308DF8296001F042FE002001F0EBFB002002F07EF9C82001F0FBFD0DEB20 :400C8000FFFC01F03BFD002002F07AF8002384F85E3001F07DFF01F04FFE74E70120FFF719FB032000F07AFC0D48FFF7ECFA0DE43F0000801B3900004B3900003892FF1F04
:400CC0000600FFF7DFFA0DF13E00FFF7FCFA01F02FFE012002F06EF9322001F0EBFD0DF12E00FFF7CFFA0DF14200FFF7ECFA012001F0CAFB4FF4967001F0DCFD01F018FE3C :400CC000553900002C3800005D3900006B3900002E38000030380000F281FF1F32380000783900002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E8C5
:400D00000DF13600FFF7BEFA0DF14A00FFF7DBFA002001F0B9FB4FF4967001F0CBFD01F007FE022002F046F9322001F0C3FD0DF13200FFF7A7FA0DF14600FFF7C4FA0120CB :400D000003008033062283F8002283E80300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EAA5
:400D400001F0A2FB4FF4967001F0B4FD01F0F0FD0DF13A00FFF796FA0DF14E00FFF7B3FA002001F091FB4FF4967001F0A3FD01F0DFFD002002F01EF90023637702F022F8F9 :400D4000450502F0B9F8013C05F003052ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F049
:400D800001F0F4FE6DE70120FFF7D8FA032000F01FFD0848FFF7ABFAFFF74BBB763B0000843B00005E3A0000603A0000F081FF1F623A0000913B000070B50024012542684B :400D80000103137012F8013C062743F0030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0E5F8072D53
:400DC000002A4ED0C368002B4BD00368013A591C01601B784260802B01D145752FE013F0800F467D0269017D12D036B1D20042F004020331026101754475026903F001030A :400DC000F5D15E485E4E002550F8041F05F1105303F1440221F0FF074533C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A00239D
:400E0000D20042F004021A43037D0261033315E046B13F2B06D9D20042F004020331026101754475026943EA0223427D036112B143F0C0030361037D447508330375037DC4 :400E000013609360136193614F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A1378DE
:400E4000072BBCD90269083B22FA03F38268511C81601370C368013BC360037D083B0375ADE770BD07B5027D42B102AA002102F8011D026001224260FFF79EFF03B05DF8D5 :400E400043F010031370464A464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4B12
:400E800004FBF0B5012100244368002B43D0C268002A40D0427D4AB183685A1C8260827D1A70C3684475013BC360EDE7027D072A0BD806680769751C05603578013B45EADF :400E8000A2F5616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F096
:400EC00007250832056143600275027D0369A2F10805EB40DBB203F0C006802E07D0C02E0ED103F03F0383754175802308E0C3F3401363F07F03033A03F08103027502E065 :400EC000030388F800302F4B48221A702E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7039
:400F00000575002BC0D08268551C85601370C368013BC360B8E7F0BD2DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F8002283E874 :400F000094E80F0007C52B80BDE8F08100480040B00900480F010049A146004025420040224200400440004006400040A2430040A04300407D390000E8460040FCFFFF478E
:400F40000300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0B7F8013C05F0030524 :400F40008400004800760040B8090048F846004020760040BC090048287600400350014070090048C05100407C09004884090048900900489C09004832510040A8090048D5
:400F80002ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013C062743F000 :400F8000CF0100491D51004001590040235B0040585B004076580040B0430040F946004008B501F0C5FF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC5E
:400FC000030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0E3F8072DF5D15E485E4E002550F8041F99 :400FC000086005E001F0B4FF0C2303604FF0FF33184608BDCC80FF1F8893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7D1
:4010000005F1105303F1520221F0FF075333C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A002313609360136193614F4B504FDF :40100000114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F090FFFFF706FAFEE700BF010000004C3B0000124A134B10B51A60124A134C1368134843F40073A8
:401040001A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A464B07CAE8 :4010400013600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E042
:4010800003C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F8100EC2F8D1 :4010800014ED00E0000000000080FF1FA10F0000BC760040C080FF1F08ED00E0F8B501F013FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E352E3
:4010C000141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B48221A70EA :4010C0001F700B3203F8946C1378054603F07F031370002001F0EAFA2378404A03F0F90323701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B3046E8
:401100002E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7094E80F0007C52B80BDE8F08102 :401100001A7802F07F021A701A7802F0BF021A7023783343237001F0C9FA2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0CAFE072117206D
:40114000004800404C0F00480F010049A146004025420040224200400440004006400040A2430040A0430040963B0000E8460040FCFFFF47A000004800760040540F00485E :4011400001F0FCFA2949172001F0EAFA0721182001F0F4FA2649182001F0E2FA0721152001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062008
:40118000F846004020760040580F004828760040035001400C0F0048C0510040180F0048200F00482C0F0048380F004832510040440F0048CF0100491D510040015900404F :4011800001F0DCFA1D49062001F0CAFA0721084601F0D4FA1A49072001F0C2FA0721082001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C20FD
:4011C000235B0040585B004076580040B0430040F946004008B501F0C3FF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F0B2FF0C230360FE :4011C00001F0BCFABDE8F84010490C2001F0A8BAA5430040944300409D60004012600040F851004084600040AD92FF1F6B1A0000A5180000691A00009D190000C9190000FE
:401200004FF0FF33184608BDCC80FF1FC893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1AA64202D05F :40120000F9190000311A0000711A0000E51A0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A60B6
:4012400041F8045BF9E701381033E5E701F08EFFFFF7C8F8FEE700BF01000000683D0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF54F8232052 :401240004FF080721A604FF400121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B51
:401280004FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E014ED00E0000000000080FF1F2B :4012800003221A70802203F8202C012001F014FE0D4B04221A7010BDC892FF1FCE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F00E100E09E60004062
:4012C000D5110000BC760040C080FF1F08ED00E0F8B501F011FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C137805464F :4012C0009C600040286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF7892FF1F0A4A002313700A4A13700A4A13700A4A82
:4013000003F07F031370002001F0EAFA2378404A03F0F90323701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B30461A7802F07F021A701A7802F02F :4013000013700A4A13700A4A13700A4A13700A4B03221A70802203F8202C7047CE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F28600040014B187899
:40134000BF021A7023783343237001F0C9FA2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0C8FE0721172001F0FCFA2949172001F0EAFADB :40134000704700BFCD92FF1F044B1A7802F0FF001AB118780022C0B21A707047CC92FF1F024A0C2303FB002040787047D492FF1F431E072B0CD8074A064B00010344805C33
:401380000721182001F0F4FA2649182001F0E2FA0721152001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062001F0DCFA1D49062001F0CAFA23 :401380005B7800F00F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032DA2
:4013C0000721084601F0D4FA1A49072001F0C2FA0721082001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C2001F0BCFABDE8F84010490C20BA :4013C00018D8DFE805F002070C110021084601F01BF80DE00021084600F0FAFF08E00021084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034BBC
:4014000001F0A8BAA5430040944300409D60004012600040F851004084600040ED92FF1F9F1C0000D91A00009D1C0000D11B0000FD1B00002D1C0000651C0000A51C0000EB :40140000185538BDD492FF1FA492FF1FAD92FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BF8B
:40144000191D0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF400121A60AB :40144000D588DB7884BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4FEC
:4014800020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8202C0120D0 :40148000385D01F011F811232946FE2218F8040001F0D6F806F5C04278321FFA89F118F8040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F0020316
:4014C00001F012FE0D4B04221A7010BD0893FF1F0E93FF1F0C93FF1F0D93FF1F0993FF1FF892FF1F0B93FF1F8093FF1F00E100E09E6000409C60004028600040126000401F :4014C0007355735D03F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFD492FF1FFC5F0040AD92FF1FA492FF1F706000402DE9F047044615468846002946
:4015000070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BFB892FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4A13700A4A30 :4015000040D0431E072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F01E
:4015400013700A4B03221A70802203F8202C70470E93FF1F0C93FF1F0D93FF1F0993FF1FF892FF1F0B93FF1F8093FF1F28600040014B1878704700BF0D93FF1F044B1A7802 :401540007FF807F5C0411FFA88F27831305D01F089F8DFF84490315D1AF8040000F0F4FF01211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F8E8
:4015800002F0FF001AB118780022C0B21A7070470C93FF1F024A0C2303FB0020407870471493FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA00200238EA :40158000093002E00D4600E000252846BDE8F087AD92FF1FA492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFD492FF1F5E
:4015C00080B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F002070C110021C6 :4015C000FE5F0040431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2ED
:40160000084601F01BF80DE00021084600F0FAFF08E00021084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034B185538BD1493FF1FE492FF1FB1 :40160000E85450680133013050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B18700870CB
:40164000ED92FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F50075ADB2C2 :4016400030BD00BF4493FF1F4093FF1F00600040BC92FF1FB992FF1FCE92FF1FCA92FF1F4193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012089
:40168000254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F011F811232946FE22D9 :4016800013707047CE92FF1FCA92FF1FB992FF1F4093FF1F4193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B155682D
:4016C00018F8040001F0D6F806F5C04278321FFA89F118F8040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F002037355735D03F0FD037355BDE82E :4016C000215C013BC9B229705168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404493FF1F0060004010
:40170000F08703FB04746379DBB28AF80230BDE8F08700BF1493FF1FFC5F0040ED92FF1FE492FF1F706000402DE9F047044615468846002940D0431E072B3FD8FFF732FF59 :40170000BC92FF1F4193FF1FCA92FF1FB992FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFCE92FF1FB992FF1FCA92FF1F4193FF1F054B9A68E5
:40174000A84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F07FF807F5C0411FFA88F278310D :401740003AB19A68044910709A680988518000229A607047BC92FF1F4493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F0CF
:40178000305D01F089F8DFF84490315D1AF8040000F0F4FF01211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E00025284675 :401780006003202B05D0402B06D043B900F012FC04E001F0A1FB01E000F046FD10B9034B03221A7008BD00BF28600040B992FF1F0060004008B5084A084B0120197813881C
:4017C000BDE8F087ED92FF1FE492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BF1493FF1FFE5F0040431E072B9FBF024B61 :4017C0000B449BB21380064B00221A70FFF7B6FF044B03221A7008BD4493FF1F4093FF1FCE92FF1FB992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE8084046
:40180000000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E85450680133013050601088E4 :40180000FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFCE92FF1FB992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFCE92FF1FCB
:40184000013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF8493FF1F8093FF1F18 :40184000B992FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFB892FF1F08600040CE92FF1FB992FF1FC1
:4018800000600040FC92FF1FF992FF1F0E93FF1F0A93FF1F8193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A0120137070470E93FF1F0A93FF1FE3 :40188000074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047CE92FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF43
:4018C000F992FF1F8093FF1F8193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B229705168DBB24A :4018C000174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078EC
:401900000131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000408493FF1F00600040FC92FF1F8193FF1F0A93FF1F07 :40190000DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BD28600040B992FF1FCA92FF1F4193FF1F29600040054A00231380054A916819B191680B701E
:40194000F992FF1F064A06231370064A01201370054B80221A70054B00221A70704700BF0E93FF1FF992FF1F0A93FF1F8193FF1F054B9A683AB19A68044910709A680988EC :4019400092685380704700BF4493FF1FBC92FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B293422A
:40198000518000229A607047FC92FF1F8493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B06D043B99A :4019800038BF0380FFF728FE012008BDBC92FF1FCE92FF1FCA92FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A73704723
:4019C00000F012FC04E001F09FFB01E000F046FD10B9034B03221A7008BD00BF28600040F992FF1F0060004008B5084A084B0120197813880B449BB21380064B00221A7030 :4019C0000B600040D492FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040D492FF1F0B4B04221A700F3B22
:401A0000FFF7B6FF044B03221A7008BD8493FF1F8093FF1F0E93FF1FF992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE80840FFF746BFB9 :401A000093F83230094B93F8242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040D492FF1F0B4B08221A700F3B93F84230094B93F857
:401A40000320FFF795FF034B03221A7008BD00BF0E93FF1FF992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BF0E93FF1FF992FF1F08B50A4B1A7832B10C :401A4000302002F00302012A1EBF93F8332082F0800283F83320012283F83120704700BF0B600040D492FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF029
:401A80001A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFF892FF1F086000400E93FF1FF992FF1F074B1B78DBB2042B05D0062B07 :401A8000000C31B3CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB0364257960
:401AC00005D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF70470E93FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D538BD5A0620 :401AC0002D0658BF84F801C090700133DBB24908D7E7F0BD9F600040D492FF1F70600040FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5BA
:401B000002D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378DBB20B70F1 :401B0000B3422A46F7D11B782B70FCB12223237001AD03232846637000F08AFE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082A48
:401B40000B7803F00F0328788342F1D138BD38BD28600040F992FF1F0A93FF1F8193FF1F29600040054A00231380054A916819B191680B7092685380704700BF8493FF1FA2 :401B4000C1700371417100F10400EAD108B070BDA73900002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F80590214449
:401B8000FC92FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FE012008BD64 :401B8000B9F1000F07D098F8044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1CC
:401BC000FC92FF1F0E93FF1F0A93FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B6000401493FF1F094B0222B3 :401BC000054BFF221A70BDE8F08300BFD492FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047C892FF1F083A0000F6
:401C00001A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B6000401493FF1F0B4B04221A700F3B93F83230094B93F8242002F043 :401C00002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF82A
:401C40000302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B6000401493FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012A1EBF93F8FC :401C40000420597891425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000FB7
:401C8000332082F0800283F83320012283F83120704700BF0B6000401493FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5144A4FEA33 :401C80000BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F807
:401CC000031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C090700133B0 :401CC00008A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288FB
:401D0000DBB24908D7E7F0BD9F6000401493FF1F70600040FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B782B70FCB1E9 :401D0000C2F307224A71083394E7BDE8F88F00BFCD92FF1FD492FF1FC992FF1FFC5F004070600040BA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC3008B
:401D40002223237001AD03232846637000F08AFE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F10400EAD107 :401D4000406908BDCD92FF1F00212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B17
:401D800008B070BDC03B00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F80440240665 :401D80001A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C368D1
:401DC0004CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE8F08300BF46 :401DC00003EB02094B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78D4
:401E00001493FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC0035869002070470893FF1F243C00002DE9F84F424B1A78002A7ED0F1 :401E0000BAF1000F06F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C20083393423F
:401E40001878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED19A7893F848 :401E400009D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE88C
:401E8000039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF4FF0070950 :401E8000F84FFFF767BEBDE8F88F00BFD492FF1FBA92FF1F4293FF1FCD92FF1FCB92FF1FD092FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F08F
:401EC0004FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F800A0B2F825 :401EC000010191700021D170517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040D492FF1FFC5F004030B4194B1A7902F0D9
:401F000008A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083394E7BDE81D :401F00007F02531E072B27D8164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D168
:401F4000F88F00BF0D93FF1F1493FF1F0993FF1FFC5F004070600040FA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BD0D93FF1F00212DE9D7 :401F40008C7006E00D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040D492FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE81
:401F8000F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187801250138FD :401F800043681B798B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDCD92FF1F00600040BA92FF1F4293FF1F38B58A4A8A4C13780021DBB24F
:401FC000C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D093F802A0D2 :401FC00021801806517840F18D800A2900F20581DFE811F05D00030103010301030103010B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B68B3
:402000000AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F0030607DA012E7F :402000006360122310E0CB78022B12D18878FFF7E5FD002800F0E180436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B28F
:402040000CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F80A5C01F05C :40204000854209D3664A91786A4AEE2908BF1346634A917881B106E0187801320028F1D018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD80B9
:402080007F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE8F88F00BF67 :402080001A78228018E0BDE8384000F025BF13F0030313D0022B40F0A0802380504B0C211B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E70222228083
:4020C0001493FF1FFA92FF1F8293FF1F0D93FF1F0B93FF1F1093FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D170517841F0B7 :4020C000514A11784F4AC9B2117053706260ACE7012323804D4BEFE70123238013794C4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB2B5
:4021000002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF006000401493FF1FFC5F004030B4194B1A7902F07F02531E072B27D8164B0C2400 :402100005AE0937803F0FF0153B9404B1A7891425FD01970404B01201870FFF715FE58E0481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB209
:4021400004FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E0012B0CBFEE :40214000D2B20A70364A3249D25CCB5C9A4240D0314B01221A70FFF753FD3AE003F00303012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B8E
:40218000082309238B7030BCFFF7DCBB002030BC704700BF006000401493FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D2012909D800 :402180002BD1214A137803F0FD0315E003F00303012B13D008D3022B1FD1114B9B78E3B9BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A79FA
:4021C000074A0848535CDBB24354A3780120DBB2535410BD002010BD0D93FF1F00600040FA92FF1F8293FF1F38B58A4A8A4C13780021DBB221801806517840F18D800A293D :4021C0005AB998781B791749DBB2CA5C22EA0002CA54BDE83840FFF79BBA002038BD00BF00600040BC92FF1FC892FF1F083A00006C3A0000F4390000DF3A00006093FF1FEA
:4022000000F20581DFE811F05D00030103010301030103010B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B686360122310E0CB78022B12D12E :40220000D492FF1F7992FF1FCB92FF1FCD92FF1FBA92FF1FB892FF1FCC92FF1FC992FF1F4293FF1FCF92FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A8093
:402240008878FFF7E5FD002800F0E180436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B2854209D3664A91786A4AEE2961 :40224000FFF786BB0020704700600040BC92FF1FCC390000014B1870704700BF78640040014B1878704700BF6B650040014B1870704700BF79640040064A0123136002F631
:4022800008BF1346634A917881B106E0187801320028F1D018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD801A78228018E0BDE8384000F065 :4022800088321268E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF79650040014B1870704700BF7D64004073B515461E460B4C05230022D4
:4022C00023BF13F0030313D0022B40F0A0802380504B0C211B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280514A11784F4AC9B21170537000 :4022C000019200920A4601461846237000F064F932462946207800F01FF90221207800F009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170F2
:402300006260ACE7012323804D4BEFE70123238013794C4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB25AE0937803F0FF0153B9404B1F :40230000A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BF7B650040704738B505460078012428B18D
:402340001A7891425FD01970404B01201870FFF715FE58E0481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB2D2B20A70364A3249D25CCB5C48 :4023400000F062FD285D0134E4B2F8E738BD08B50D2000F059FDBDE808400A2000F054BDF7B516461F460B4C00230325019300930A4601462846257000F00EF93A463146E4
:402380009A4240D0314B01221A70FFF753FD3AE003F00303012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0FD0315E0C0 :40238000207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F0F2F83A463146207800F0ADF82946CC
:4023C00003F00303012B13D008D3022B1FD1114B9B78E3B9BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A795AB998781B791749DBB2CA5CC8 :4023C000207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207805
:4024000022EA0002CA54BDE83840FFF79BBA002038BD00BF00600040FC92FF1F0893FF1F243C0000883C0000103C0000FB3C0000A093FF1F1493FF1FB992FF1F0B93FF1F4D :4024000003B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1F72
:402440000D93FF1FFA92FF1FF892FF1F0C93FF1F0993FF1F8293FF1F0F93FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A80FFF786BB002070470060004007 :40244000024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD838
:40248000FC92FF1FE83B0000014B1870704700BF7F640040014B1878704700BF69640040014B1870704700BF78650040064A0123136002F688321268E0211064034A1170C4 :40248000084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25451
:4024C000A2F540721360704780E100E000E400E0014B1870704700BF72640040014B1870704700BF7665004073B515461E460B4C05230022019200920A460146184623706A :4024C00000207047012070471070004017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C490093
:4025000000F064F932462946207800F01FF90221207800F009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170A2F202321360704780E100E029 :4025000003F0F10301F00E011943815400207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC28032
:4025400002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BF78640040704738B505460078012428B100F060FD285D0134E4B2F8E706 :40254000002001207047000017289FBF034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF147000404E
:4025800038BD08B50D2000F057FDBDE808400A2000F052BDF7B516461F460B4C00230325019300930A4601462846257000F00EF93A463146207800F0C9F80221207800F030 :4025800017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213C5
:4025C000B3F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F0F2F83A463146207800F0ADF82946207800F097F8207803B0F0BD6F :4025C000DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F075FC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F0A3
:40260000E180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207803B0F0BDE280FF1F73B515466E :4026000067FC204610BD00BFE480FF1F030610B5044611D400F058FC084AE300117803F1804303F5F04319705378147001335370BDE8104000F04CBC10BD00BFE480FF1F18
:402640001E460B4C0023019300930A4601461846237000F0BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1F024B1878C0F38010704700BFFD :4026400030B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC2800020012070470000CD
:402680008F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200204 :4026800038B50446084DB4F5004F05D9286800F013FCA4F50044F6E7034B58686043BDE8384000F009BC00BFEC80FF1F024B1B7A584300F001BC00BFEC80FF1F0E4B00F0E7
:4026C00001E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004028 :4026C00003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B012238
:4027000017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E0119438154A7 :402700001A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0049
:4027400000207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF73 :4027400000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A5C
:40278000034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F051 :402780001360084B4FF400421C60C3F8E82010BD7C92FF1FFD27000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A7F
:4027C000070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F08F :4027C000136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF7C92FF1F024B1868C0F30040704700BF10E000E038B5FFF7EC
:402800000F03A370E07010BD012010BD10B500F073FC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F065FC204610BD00BFE480FF1FD1 :40280000F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF8092FF1F024B03EB80035868596070477C92FF1F134B144A1B78DBB20360127843EA0223B0
:40284000030610B5044611D400F056FC084AE300117803F1804303F5F04319705378147001335370BDE8104000F04ABC10BD00BFE480FF1F30B504060CD411F4704509D14E :40284000114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BFF1
:40288000C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D98C :402880000301004904010049EC460040020100490101004900010049050100490601004910B500F011FB204A044613780A2043F002031370137C43F00203137412F80A3C08
:4028C000286800F011FCA4F50044F6E7034B58686043BDE8384000F007BC00BFEC80FF1F024B1B7A584300F0FFBB00BFEC80FF1F0E4B00F003001A78490102F0FC021043EC :4028C00043F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062C7C
:4029000018701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F090 :40290000A3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D4BAAB4300400E5900402F5B004080E200E008B500F0C5FA0F4A3B
:402940000F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B29F :40294000137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0ABBA00BF0859004037
:40298000017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F887 :40298000044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F9101028
:4029C000E82010BDBC92FF1F312A000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF9F :4029C0000A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470A590040B83900004A93FF1F4C93FF1F5093FF1F08B5102000F0A6F974
:402A000010E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BFBC92FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F859 :402A000007210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BDF12A0000095900404893FF1F10B5054C23781BB9FFF7DCFF7A
:402A4000243003B198470134052CF8D138BD00BFC092FF1F024B03EB8003586859607047BC92FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A93 :402A400001232370BDE81040FFF72ABF9892FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7A2
:402A80000360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC460040B6 :402A800015FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF5093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B61
:402AC000020100490101004900010049050100490601004910B500F00FFB204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F01F :402AC0001B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270474A93FF1F4C93FF1F4893FF1F7047000010B500F0E7F9214A0446137861
:402B00000103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A19 :402B00000A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400201
:402B4000137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D2BAAB4300400E5900402F5B004080E200E008B500F0C3FA0F4A137803F0FE031370A2F5AA523E :402B400003F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0A9B900BFB0
:402B80001D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0A9BA00BF08590040044A137803F03F0343EA8010C1 :402B8000AB43004006590040275B004080E200E008B500F099F90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F87F
:402BC000C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210736D :402BC0000A3C937903F0FD039371BDE8084000F07FB900BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422383
:402C00004B4341F2883103F6C41393FBF1F305490B60054B1A8070470A590040D23B00008A93FF1F8C93FF1F9093FF1F08B5102000F0A6F907210420FFF79AFE074904204B :402C000008380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470259004054
:402C4000FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BD252D0000095900408893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABF84 :402C4000C23900005693FF1F5C93FF1F5493FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BD33
:402C8000D892FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF715FE024B1B78204610BD00BFC6 :402C8000492D0000015900405893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BF9992FF1F044B1A7802F0FB021A701A7842F001021A70704700590040D8
:402CC00009590040034A044B1B881088181A00B2704700BF9093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FBE0 :402CC00010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5493FF1FA05B004034
:402D000000F2022391FBF3F30028D8BF5B42134493FBF1F000B270478A93FF1F8C93FF1F8893FF1F7047000010B500F0E5F9214A044613780A2043F001031370137C43F09E :402D00000E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475693FF1FD5
:402D40000103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0215 :402D40005C93FF1F5893FF1F70470000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040014B1870704700BF7A65004015
:402D800003F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0A7B900BFAB43004006590040275B004031 :402D8000014B1870704700BF7E64004073B515461E460B4C04230022019200920A46014618462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDA7
:402DC00080E200E008B500F097F90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8E0 :402DC000FC80FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF7A640040014B1870704700BF9D
:402E0000084000F07DB900BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF20 :402E00007B64004000000000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C4601254A
:402E40000C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A80704702590040DC3B00009693FF1F9C93FF1F77 :402E4000002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B78234041
:402E80009493FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BD7D2F0000015900409893FF1F31 :402E800003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BFAA
:402EC00010B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BFD992FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D1E6 :402EC00001380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E013790020704714
:402F00000028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF9493FF1FA05B00400E4A13881BB223B111880A23E0 :402F000000600040D492FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B57A
:402F400009B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270479693FF1F9C93FF1F9893FF1F7047000060 :402F40001E460E4C0025E41AA410A54204D056F8253098470135F8E700F0DEFD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD243B0000243B00007F
:402F8000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040014B1870704700BF76640040014B1870704700BF7C6400403B :402F8000243B00002C3B000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029350
:402FC00073B515461E460B4C04230022019200920A46014618462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDFC80FF1F074A0223136002F656 :402FC00005934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF741
:4030000088321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF74650040014B1870704700BF77640040FEB5494652465B4644 :40300000CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BF75
:403040000EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C460125002600F041F8814651460B7823400B7094 :403040001D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A38908
:403080005846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B78234003430B705846043000F016F8484600F0FE :403080004FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F087000061
:4030C0001FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BF01380046FCD17047EFF3108072B6704723 :4030C0002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB1302302
:4031000080F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E0137900207047006000401493FF1F002902D0B0FBF1F039 :403100008DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A080002335
:40314000704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B51E460E4C0025E41AA410A54204D056F845 :403140004FF0FF3204930793059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A0773
:40318000253098470135F8E700F0D4FD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD403D0000403D0000403D0000483D000003460244934202D071 :4031800044BF2B228DF8532022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB0232012141
:4031C00003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029305934FF6FF7300910491ADF80E3002466A :4031C000F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB46
:403200001E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF7CBFF03B05DF804FB6081FF1F2DE9F04781 :4032000001210123F5E703B1059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039BDF
:403240008E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BF1D4653050FD5294600F04AFB064698B178 :40324000073323F007030833039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB897F
:403280003A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A3894FF0FF3043F04003A381BDE8F0872661F3 :403280005B0601D4099801E04FF0FF301DB0BDE8F08F00BFF33A0000F93A0000FD3A0000000000000D3000002DE9F04791461F460A698B6806469342B8BF1346C9F8003093
:4032C0003E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F08700002DE9F04F9DB003938B8980461C060D4643 :4032C00091F843200C46DDF8208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD001355B
:4033000016460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EBAA :40330000E368D9F800209B1A9D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430253
:40334000060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A08000234FF0FF3204930793059206938DF85330B6 :4033400039463046C0470130F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D0012322463946F2
:403380001A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A0744BF2B228DF8532022782A2A03D0079A60 :403380003046C0470130D5D009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A01
:4033C00000210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB02320121F5E701B107923B782E2B1ED17B782A2B3F :4033C00040F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F114
:403400000AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB01210123F5E703B10591032239782248C1 :403400004205111D1960136884F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E0C5
:4034400000F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039B073323F007030833039314E003AB0093EC :4034400021681A6811F0800F02D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F0E
:403480002A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB895B0601D4099801E04FF0FF301DB0BDE802 :4034800000F104051D6003D1550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F0CC
:4034C000F08F00BF0F3D0000153D0000193D0000000000003D3200002DE9F04791461F460A698B6806469342B8BF1346C9F8003091F843200C46DDF8208012B10133C9F8EF :4034C000040020602BB9002D7DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013CF7
:4035000000302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD00135E368D9F800209B1A9D42F1DB94F84330E8 :4035000005F1FF35C5EB0E0323612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111DFC
:403540002268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430239463046C0470130F4D02268D9F8005010 :403540001960156800216268284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF056
:40358000E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D00123224639463046C0470130D5D009F10109F3E70020FB :40358000FF3026E023692A4639463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842DC
:4035C000BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520834955E0642A1ED03B :4035C000B8BF184605E00B7804F1420584F842308AE705B0BDE8F083A7390000043B000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431EF7
:40360000692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F14205111D1960136884F84230A8E021683E :403600000A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B40
:403640001A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E021681A6811F0800F02D0111D196007E0F0 :4036400002F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B68121885
:4036800011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F00F104051D6003D1550601D5038800E0E0 :40368000226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018A3
:4036C0000368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F0040020602BB9002D7DD175460CE0002BBC :4036C000824201BF106852680918216062605C602846BDE8384000F098B838BDA092FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E018
:4037000079D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A68FA :4037000000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304658
:4037400010F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111D1960156800216268284600F049F808B1F0 :4037400000F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F863
:40378000401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF0FF3026E023692A4639463046C0470130EF :403780000130EBD10C233360304600F03EF8002070BD00BFA092FF1F9C92FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F8854208
:4037C000F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B7804F1420584F8423091 :4037C0000ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7DCFB431C02D1236803B13B
:403800008AE705B0BDE8F083C03B0000203D000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431E0A44914204D011F8014B03F8014FF8E76E :403800002B6038BD8493FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F021102130215027265706C792030782530327881
:4038400010BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B02F8014FF8E710BD38B50546002944D007 :4038400000686F6D696E6700626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E6973686564207365656B00796573006E6F00647269766520AA
:4038800051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B681218226063600C6023E0A24203D813465A6820 :40388000303A20257320647269766520313A2025730057616974696E6720666F72205553422E2E2E0055534220726561647900636F6D6D616E64203078253032780066610F
:4038C000002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018824201BF106852680918216062605C6019 :4038C000696C2025642B25642B2564203D3D2025642C206E6F74202564007061737365643D256400756E64657272756E206166746572202564207061636B65747300636FE8
:403900002846BDE8384000F098B838BDE092FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E000F082F8234B1C681A462146A1B10B6883 :40390000756E743D256420693D256420643D256400636D645F777269746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D2564207526
:403940005B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304600F052F820602946304600F04DF8431CCB :403940006E64657272756E3D256400756E64657272756E2100737563636573730073746172742065726173696E670073746F702065726173696E670069646C650000510001
:4039800018D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F80130EBD10C233360304600F03EF80020E9 :4039800040100040510040300000000140001000140140000800400140000A004C014000020050014020003031323334353637383941424344454600000100000004000096
:4039C00070BD00BFE092FF1FDC92FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F885420ED929463846FFF78BFF044650B131469B :4039C00000100001000000040000001028000000000104000100000000000000000157494E5553420000303030303100000000000000000012034D0053004600540031002A
:403A00002A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7DEFB431C02D1236803B12B6038BDC493FF1F7047704751F8040C50 :403A0000300030000100000001000000103A000001000000DF3A0000000000000000000001000000283A000001000000B13A0000040000004A3A00000000000000000000E9
:403A40000028BEBF091851F8043CC0180438704700000000050209020B020D020F021102130215027265706C792030782530327800686F6D696E6700626567696E6E696EEF :403A400000000000483A0000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E006700CA
:403A800067207365656B2066726F6D20256420746F2025640066696E6973686564207365656B0057616974696E6720666F72205553422E2E2E0055534220726561647900AF :403A800069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FFC9
:403AC000636F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F74202564007061737365643D256400756E64657272756E2061664A :403AC00000000107050102400000070582024000000705030340000A0705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C00656667E2
:403B0000746572202564207061636B65747300636F756E743D256420693D256420643D256400636D645F777269746500646F6E6520256420256400703D25642063723D25BB :403B0000454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E467047590000002D100000F8B500BFF8BC08BC9E46704735000000503B0000FD
:403B4000642063773D256420663D256420773D256420696E6465783D256420756E64657272756E3D25640077726974652066696E6973686564007374617274206572617393 :403B4000C880FF1FA00000002012000000000000000000008893FF1FFF000000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D00FA000040000000DA
:403B8000696E670073746F702065726173696E670069646C650000510040100040510040300000000140001000140140000800400140000A004C014000020050014020003F :403B80000090D003FF0000000000000000000000000000000000000000000000000000000000000000000000F13A0000000000000000000000000000000000000000000078
:403BC0003031323334353637383941424344454600000001000000040000001000010000000400000010000028000000000104000100000000000000000157494E555342F2 :403BC000000000000000000000000000000000000000000000000000000000000000000000000000000000000081FF1F000000000000000000000000000000000000000026
:403C00000000303030303100000000000000000012034D0053004600540031003000300001000000010000002C3C000001000000FB3C000000000000000000000100000010 :403C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084
:403C4000443C000001000000CD3C000004000000663C0000000000000000000000000000643C0000FF00000001024000FF00000082024000FF00000003034000FF0000002B :403C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044
:403C800084034000FF00020304030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F0096 :403C80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
:403CC0006C006F0067006900650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000A0705840340000A12010002FFD7 :403CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C4
:403D00000001080912006E0100020180014300232D302B2000686C4C00656667454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E46704759 :403D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083
:403D40005900000061120000F8B500BFF8BC08BC9E46704735000000683D0000C880FF1FA00000006012000000000000C893FF1FFF000000675000400C0000000700000024 :403D40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043
:403D8000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003FF0000000000000000000000000000000000000000000000000000000000000000000000B0 :403D80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
:403DC0000D3D000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000079 :403DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C3
:403E00000081FF1F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E3 :403E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082
:403E40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042 :403E40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042
:403E80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002 :403E80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
:403EC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C2 :403EC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C2
@@ -4098,68 +4098,68 @@
:40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041 :40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041
:40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 :40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
:0200000480007A :0200000480007A
:400000000145004008520040015B004001650040010101404D0201403D0301404C0401404D0501404D060140500701404B0801404B0901404B0A0140500B0140470C0140CB :400000000145004008520040015B0040016400400101014003050140030701404E0801404A0901406C0A0140550B01405A0C0140490D01405A0E0140340F0140041701403E
:40004000500D0140520E0140350F014046140140541501404B160140561701404718014044190140591A0140571B01400A400140104101400E4201400943014004440140BE :40004000471801405A1901404F1A01404C1B01400C4001400A4101400F4201400D430140044401400C450140064601400C4701400F4801400B490140194C0140074D0140FA
:400080000D4501400A4601400F470140094801400F4901401B4C0140084D014008500140045101407E0208440905108011821801600C610A7C4027212A0A8C8000010109B1 :4000800005500140045101407E02080509411080118219026006610A7C4027212A0AE280E640EA05EE10E620EA01EE8C00180220041806E009020A100C010E02100111076F
:4000C0000202040805090808090F0B100C080D010F02100F1120140416011709180819101B0E1D011E01210422082301240829092A0E2C0D320F331F35203B083E043F1020 :4000C00012041407150216F8170118801A181B011C181E4020FF260828012A062E072F0630FF330335043B083E01580459045B045C905F0181408210833085E18680870885
:400100005608580459045B045C905D905F01850287018C01900F9401960898249A089E07A008A101A222A302AA08AE10B01FB101B302B420B90ABE10BF05C202C70EC810AC :4001000088018A248B108C7C8E828F1091FC92039302961097039B109D209E109FDCA180A210A330A401A648A9E1AA1CAB04AE10B01FB31FB4F0B7E0BA20D608D80BD90B5D
:40014000C9FFCAFFCBFFCF83D804D904DA04DB04DC09DD09DF01030204A10620074008010A060B200C800D100E400F2010421110150416821720180419011E10202021010F :40014000DB04DC99DD90DF01012003A20404055208040AA10B010D080EA80F0210201280135015041680175919011A801B301E402180228424202514260429202B802F0167
:40018000262027202A012C042E023508376138083E583F0240045208588859215A025D4060026108621265806F0180068140874088408A088E209880C0F1C2FFC4FBCAA1EC :400180003180321433023614370138083B513D2058105980600A68026B046D4078018410884091209358942C9540970199729B599C019F04A240A301A58CA628A792A901F2
:4001C000CCF0CEF2D004D61FD81FE20CE6C5EE82010D04020601070109080C010D080E0E100F130E150F161019081C201D082101220F2302240125042604270128012908CA :4001C000AA10B010C0FFC2FDC4FFCA15CCEFCE2FD60CD80CDE01E622E804EE8C004801400311044805F10708081009100A200BE10C5A0E200F0E10C01201144818481C02F7
:400200002A082C012F08301F3220330F3E043F045608580459045B045C095D905F01810182088302840885028A108B018C088D038F049102920194049701982099029A1FB5 :400200001D201E041F112180224023112448288629F12A782B022DF12E042F04303F310F328033F0344038023B023E1440254520480249FF4AFF4BFF4D204EF0511058045A
:400240009C089D02A202A302A408A501A704A808A902AC1FAD03AE20B107B43FBF01D804D904DB04DC09DF0100400108048006A0070809880A400C810E080F20100511082C :40024000590B5A045B045C995D095F01610862406340648066406740800481018220830E8404861088808A048BC08D018E0291EF92029403952F96FC974099019A049B04BE
:40028000130115251740185019011B011E081F20210827402AA02B902C052D022F20308031213208350136803728388039243B013D083E4A3F2158405C105D806180851076 :400280009C049D809E409F2FA0FFA101A302A404A6F9A710A804A901AA08AB08AC01AE02AF20B4FFB51FB7E0BE10D804D904DB04DC90DF01004401020204042405020606B2
:4002C00087029102938094A195349644972099809B209C0C9D089EBAA480A602A713A908AC03B010B520B601C0F5C2FDC4FFCAFFCCFFCEFFD638D808E20AE648E880EA03A0 :4002C000084809200A800B280D280E800F021180128813081408174918401A041B051F2023502680270829202B822C802E802F28318034403608375138083B513D903E02AA
:40030000EE820003010D050506010801090C0B020C010D0D1001111012021602170D1801190D1C011D0D2201230D2401250D280129092C012D012E022F02310C3510360355 :4003000042044620470849014A0268056E507801830489108A808E409120928093209540965099729A829B519C019D019E209F04A090A160A242A308A405A50CA628A7130E
:4003400037033E403F51580459045B045F0180208102820F8404860A882F89028D028EA09204948095039640970C98049A099E10A020A102A20FA480A802AC01AD0AAF0550 :40034000A840AA01B540C0FFC2FFC4FFCAFDCCF8CEBFD004D208DE01E404E640EE1000800102027E044006100702083E09010A400C400E020F041040111812201320147E22
:40038000B060B108B21FB306B480B501B61FBE10BF01D804D904DB04DC99DF01000902080302048A0708082209880C800D100F9011501305158016A01710180919041A08AD :400380001703188019021C401D081E04210222012502284029022A082D202F1830FF310833203507371039203E013F05400247EC483049FF4AFF4BFF5004560158045904C1
:4003C0001BD21D80210923042440254026102B422C042D012E402F20310836843721380239803A103D203E083F825D015F0169506AA06F0178017B807D027F8080048604BE :4003C0005A045B045C905D095F0162C080368209842D8612880789028B018C3F9209961299019B029C24A104A480A640A840AA80AEC0B040B104B238B301B407B680B7021B
:4004000089108A028F01938094A19525964497219B209C0D9D0A9EA89F08A321A480A520A68AA790AD01B040B210C0FFC2FFC4FFCAF9CCF2CEFDE242E401EA01000103027E :40040000B882B988BE41BF45D80BD904DB04DC09DF01001A010403820408060A07400A700B410C800D280E80102011501711189019A01C80202021402202253026802760C1
:4004400004E0050108010A080B020CE00F02100F1280130814481602170218201B021E071F02200422482302261027022A082B042F10311F34E0361F382039023F015804DF :4004400029012D022E202F11302036A939103C803D283F02472056056A806B016C946D806EA06F18768A780189C08A1091309323942895049620974099129E249F45A0205B
:4004800059045B045C095F0180038109840387318A018B098C038F06900391129309940395099712980399409E02A003A309A603A709A803A909AC03AD49AF24B338B40349 :40048000A201A480A55CA668A79BA820A950AA80B080B640C0FFC2FDC457CAF1CCF4CEF4D020DE0100020140020C04100550072008040A080E010F10107211041280130203
:4004C000B540B707BB88BE10D804D904DB04DC90DF010080012002800320048A052008180A010B400C020D080E0610021325172918081E061F802002215423252480284155 :4004C000140E150816F018101B201C801E20201021602310244025022680270528102C082D072E0430FF310133703506370838023F445608580459045B045C995D905F0174
:4005000029202B202C872D202F203108321233013448350137A0380239883B103C803E043F115A806E408340860287048A0690029289935094019704981A99019B889C84CD :40050000810C82078340840185808604870C88018A028B048C408EA08F02900191FF9457950397FC9840990C9A909B109C019D019E069F02A057A302A50CA7F1A840AB0883
:400540009D209F20A0C2A108A280A328A401AB10B204C0FFC2FFC47FCAFFCCF7CEFFD608E080E620EA85EE2002800404050D064808200A400C0111011210144816021A07E2 :40054000AD0CAE08AF20B030B1FFB40FB6C0BF01D804D904DC09DF010220034904280540074009180A410C800D080E881004118812801604178119021A401D501E021F114A
:400580001B051C401E202608280129102A082C0F2D0A3020310C32C03303341F362037103A083E413F4056085804590B5B045C995D905F01822083018607870F88218A0825 :400580002112224427802B922C842E602F103102321033043628374139043B413DA83E013F025880630169908A048E048F01C0FFC2FFC4DFCAFBCCF7CEFBD608D808E280A8
:4005C0008B018C218F019210930E9428964297059AC89B039F01A02FA280A301A444A628A709A90FAA20AD0FB105B21FB4E0B503B709BA20BF51D804D904DC09DF010001EA :4005C000E404E680EA08EE0B011406410714080109140A120B080CF10E040F201241150A16411714180119041A281C801D0A1E01211422412504264E29042B102D182F0158
:400600000184030805A4078009090A060D080E881006121015141701180219811A051B481D041E081F20210422412501264027082B052C822F2432113408350137A03908B7 :40060000320F3307353836F039083A085608580859045B045C995D905F018210846088018A048C7C8D018E02921C96039A109C409D04A210A401A608AA10AD02AE10B1016E
:400640003C823D043F144410450858805B10630A64086610780284018601911092289358950896019704980A9B819D249F20A0C2A108A280A401A761A840AF11B040B504F1 :40064000B21FB304B440B502B620BF15D80BD904DB04DC09DF01012803810524070108100A120B800C200E060F50102011011280135014101501170418081A161E021F885A
:40068000B702C0FFC27FC477CAFCCCF5CEF2D60CD80CDE01E202E640E880EA10EE4002080349077F08010A280B090C2F0F091001117F1408160217091A071B091E401F2962 :4006800020202101221024042518262828022D062E802F0430043240330C35103608374039803D283E403F0258405A205D505F086401650867066A806C016F0378017F01D2
:4006C000221023722709280429782A282B042F19310732603348341F352837183F55580459045B045C095F01802081048440850186A088418A088C4F8E808F04920794041B :4006C000814083088501912492489358940C96019702984299339A809B409C059D80A104A280A301A460A508A628A782AC02B048C0EFC2FFC43FCAF8CC78CEF8D67CD870F3
:40070000966898689A029B019C41A208A610AA40AD02B01FB304B503B6E0BA80BF04D608D804D904DB04DC99DD90DF010128024003010521070109040A810B080D080E0633 :40070000DE11E204E402E680EE0B01FC02900302042005C106C0072808C10A240B900CFC0E020F901120120313C018011A481B901E901F032290239C2690279029012A9012
:400740000F80128013181608170218821A801D081E041F14221423102601271A2B012D042E012F253210341036843721380239083A403C023D1A3E0A3F54584060026240FE :400740002B442E9C2F9031E034E0361F371F3A203B02420245E0480249FF4AFF4BFF4F83580B590B5A045B045C995D095F01821084618501860888618A04921096109A0394
:4007800068026B016F0A831886408B048C018D089342951496019714980899019B809C109D209E0A9F21A0C0A284A402A509A74AB308B504C0DFC2FFC45ECAF8CCF4CEFBE6 :400780009C409E30A07CA202A61CA820AA50AC80B080B101B260B41FB660BE01BF01D808D904DB04DC09DF01012002C0036204080520074008020A050C400E0A0F50105066
:4007C000D608D808E010E280E644EE820002012F020103800402060107070A060B200C020E010FC81002120114071528174218021A011B101C021E012121220523082544DD :4007C0001350141015401608170519401B301C041D041E491F4022422780284029242A4830103242380A3B503F044110484059015A985F80600661406202632069806D40BA
:400800002607272829212A012C0731E03207331F3B023E04580459045C905F0182698502861988198E198F119019924493049419962298229A199B089C19A20EA306A61923 :4008000078017F018240900A9120921C9356958096019780980199219B059D04A281A302A440AD10B010B580C07FC2FBC4FFCA0FCC0DCE4FD004D61FD80FDE11E004EE085F
:40084000A702A910AA19AB01AD01AE19AF10B070B208B301B407B510B607B70EB928BAA2BB80BE04BF14D804D904DB04DC99DF010082012402400521060207200810094941 :400840001A401F083328360437103A406A406B108B20C630CCF0CE1032043380364037023A013E809E40A604CCF0CE60502055108D2096089EC09F02A020A110A644A7809D
:400880000B200C020D090E010F04104213141514164017801A201C821D011E281F012010210423C025442680270829202A082B402C802F1A30803108329034483614378162 :40088000A910AA01B420D460EAA08A809EC09F02A002A120A202A640A780AA06AE04B002EA80EE3014107310C404DC01530164108B1090109710B010B410D401D801E4017C
:4008C000390A3B503D403F046D408C10C0FFC2FFC4FFCAF7CCA6CE5FE210010E04080508060409080C0D0E020F08110812091504180819081A021D081E04200423012702A7 :4008C000E808EA041B048301901097029C10C60808080B080E020F408710903097089C10A810AB04AF04C20F240280018A409A209E40A002A120A202AB02AE60AF80C820D3
:4009000029082D08300C320C330F34033A0A3F045608580459045B045C095D905F01801382048402871088028B1F8C088E018F1F901991019202951F96099A089B049C0273 :40090000E420EA10EED051205202722074029A20A002A120A202D4A0DC80DE2005400B200C800E401F40501056205C106120824084108B40903097209C10AA01AC08AF40A1
:400940009D1FA001A20CA308A602A720A802AB02AC1BAD1FB210B40FB73FBB80BE14D804D904DC90DF010108021104020540060807200A040B810D110F2211021405161004 :40094000C001C20DC601D406D604D802E60182209E20AD40B120B480EC01EE0201010D010F0111011D0100FF01AB020211050000BF0000A09F001F0000000000000000004A
:400980001702190A1A041E201F20210425402680270229442A212C802E202F2130803120320433113545371039163A803B033D843F2058806002680D6A508101821084A1C0 :40098000100000004000000000000000C0000000FF0000B84700470000010000800000028200820000000000000303000300000027001801270018010004000000050000C9
:4009C000880289028B048E5090029134922893859508980599459AA99B219C229D0AA080A121A311A40CA690AA88AD80AF10B111B504B648C0F5C2FBC4F8CAFFCCFFCE7FAE :4009C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F7
:400A0000D608D808E248E401E602E809EE030001062008010A7E0D010E08107F167F1A041E1020022101247E26012A402C7F3501367F3B303E40580459045B045C905F0179 :400A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B6
:400A4000804484118708881189078C078E088F109211941195A5974298119B429C119F21A011A307A433A5C6A644A721A878AA02AC20AD84AE02B3E0B51FB67FB71FB9A0BC :400A40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076
:400A8000BE40C004C5E0C6C0C80AC9FFCAFFCBFFD004D601D804D904DA04DB04DC90DD09DF01E2C001160201030204020540071409A80B400C810D200F2010041141130440 :400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036
:400AC00015A016A019801F80214023152510270428222B402F8030083250384039083B103D8041014B025210688069756B116C016E406F02705071E972A07348810A8204C4 :400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6
:400B00008404850487048A108C509040910292A8938094A295A496409733980199049B209C269D0A9EB89F18A141A351A484A530A68CA808B210B540C0FFC2FFC4FFCA8B79 :400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5
:400B4000CC0ECE1ED001E003E208E402EA40EE060601083109100D0C0E080F021040130116021702180319011A101E0420382304240325082608290A2B042C022E2130400E :400B40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075
:400B8000310F3207351036383A803E013F10580459045B045C995F018001830C8401870188018A028B078C018F019001930194019701980199079E019F01A001A108A302BA :400B80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035
:400BC000A601A701A801AB01AC01AE02AF01B507B603B708BE40BF50D804D904DF0100820201032A05480608081909800B400D080E461002128013241501174818011D4E2B :400BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F5
:400C00001E01218022102408260228082BA22C082E022F103011328436063710380239A83D103F806D5080048280850186058C208D01900291A89248934494019620981A26 :400C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B4
:400C40009A029B609C809D20A043A284A328AB80B604C0EFC2FFC4BFCAEFCCEFCE3FE080E451E8C0EE02000801010201050108040B010C010D010E081101130217011901F5 :400C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074
:400C80001A011D01210123022401250129012A022D01300332043303340836083A023E543F0440434502480149FF4AFF4BFF4D204EF05110580459045A045C095D095F017F :400C80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034
:400CC0006108624063406480664067408004840885CC86018722881F8C048D01900493119401961E9722980499F19C019E10A004A344A588A604A824A9AAAA19AB44AC0134 :400CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F4
:400D0000AE22B020B30FB50EB61FB7F0BE41BF10D804D90BDC90DF01002001020301058907080B040C200D090E011010120816801764181019221A0A1B801D021E0222083E :400D000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B3
:400D400025602711280929022B202C082E022F10301232843606371039A83A013D884010410248107C0283408520881089088B308C088D448F4091EA920193049602972033 :400D40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073
:400D8000981B99819A839B0C9D209F10A022A108A286A320A408A601A7C0A904AB04AD01AE01B080B18AB608B708C0FBC2F2C4F6CAEFCCEFCE5FD003DE80E0A0E240E47094 :400D80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033
:400DC000E602E890ECC01B011F0231203302368037083B40C630CCF0CE103088364037043A023C2088108F0893809F08A520AE80AF41B340CCF0CE60EE4053405640840897 :400DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F3
:400E00008C8093809A409E029F04A488A520A6C0B2C0D460E2408E0292029E02A520A6C0AB04AE01EA80EE4015027B02C404DC015940670882108E4191029720AF10B7020E :400E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B2
:400E4000D401D801E008E602EC021A4085408D049102966097289D40A201C608E60209440F4191029420962097299C409D40A201A544A810AB01B040C20FEA0126809202EF :400E40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000072
:400E8000A320A520A680AE40B720C820EE405280572079407E01828085208D409202A320A520D460DC80DE20E210E62004200A200C400F401F1050805B405E025F8083408D :400E80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032
:400EC000894091029420962097209C409D40A201AF40B304C001C20DC601D405D605E001E801850188809102A480B3C0E401EA04EC04010109010B010D010F0111011B01BF :400EC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F2
:400F00001D0100FF01AB020211050000BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B847004700000100008000000282008200D5 :400F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B1
:400F400000000000000303000300000027001801270018010004000000050000000000000000000000000000000000000000000000000000000000000000000000000000DF :400F40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071
:400F80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031 :400F80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031
:400FC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F1 :400FC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F1
:4010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0 :4010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0
@@ -4615,12 +4615,12 @@
:0200000490105A :0200000490105A
:04000000BC90ACAF55 :04000000BC90ACAF55
:0200000490303A :0200000490303A
:02000000A35308 :02000000861365
:0200000490402A :0200000490402A
:4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0 :4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0
:400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080 :400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
:400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040 :400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040
:4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 :4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
:0200000490501A :0200000490501A
:0C00000000012E16106900002E31B3BC68 :0C00000000012E16106900002E2F967CC7
:00000001FF :00000001FF

View File

@@ -848,6 +848,7 @@
<Data key="efd5f185-0c32-4824-ba72-3ceb5356f5a7" value="Clock_1" /> <Data key="efd5f185-0c32-4824-ba72-3ceb5356f5a7" value="Clock_1" />
</Group> </Group>
<Group key="Pin"> <Group key="Pin">
<Data key="3e1862bb-be82-47b0-9549-7ebfe76b6f7b" value="Pin_2" />
<Data key="4a398466-709f-4228-9500-96178658e13e" value="RDATA" /> <Data key="4a398466-709f-4228-9500-96178658e13e" value="RDATA" />
<Data key="5a3407c1-b434-4438-a7b4-b9dfd2280495" value="MOTEA" /> <Data key="5a3407c1-b434-4438-a7b4-b9dfd2280495" value="MOTEA" />
<Data key="8d318d8b-cf7b-4b6b-b02c-ab1c5c49d0ba" value="SW1" /> <Data key="8d318d8b-cf7b-4b6b-b02c-ab1c5c49d0ba" value="SW1" />
@@ -858,6 +859,7 @@
<Data key="472f8fdb-f772-44fb-8897-cc690694237b" value="WDATA" /> <Data key="472f8fdb-f772-44fb-8897-cc690694237b" value="WDATA" />
<Data key="736cb12b-c863-43d4-a8f0-42f06023f8b5" value="SIDE1" /> <Data key="736cb12b-c863-43d4-a8f0-42f06023f8b5" value="SIDE1" />
<Data key="4249c923-fcff-453b-8629-bec6fddd00c1" value="STEP" /> <Data key="4249c923-fcff-453b-8629-bec6fddd00c1" value="STEP" />
<Data key="27315b0e-6a8c-4b7f-be77-73ab434fa803" value="Pin_1" />
<Data key="1425177d-0d0e-4468-8bcc-e638e5509a9b" value="UartRx" /> <Data key="1425177d-0d0e-4468-8bcc-e638e5509a9b" value="UartRx" />
<Data key="a5d987c6-e45b-45b9-ad93-656fab06d720" value="TRK00" /> <Data key="a5d987c6-e45b-45b9-ad93-656fab06d720" value="TRK00" />
<Data key="a93ef5b3-00f4-42c0-8fad-0e275a7e2537" value="MOTEB" /> <Data key="a93ef5b3-00f4-42c0-8fad-0e275a7e2537" value="MOTEB" />
@@ -3963,6 +3965,11 @@
</Group> </Group>
</Group> </Group>
<Group key="Pin2"> <Group key="Pin2">
<Group key="3e1862bb-be82-47b0-9549-7ebfe76b6f7b">
<Group key="0">
<Data key="Port Format" value="0,6" />
</Group>
</Group>
<Group key="4a398466-709f-4228-9500-96178658e13e"> <Group key="4a398466-709f-4228-9500-96178658e13e">
<Group key="0"> <Group key="0">
<Data key="Port Format" value="1,5" /> <Data key="Port Format" value="1,5" />
@@ -4064,6 +4071,11 @@
<Data key="Port Format" value="1,0" /> <Data key="Port Format" value="1,0" />
</Group> </Group>
</Group> </Group>
<Group key="27315b0e-6a8c-4b7f-be77-73ab434fa803">
<Group key="0">
<Data key="Port Format" value="0,7" />
</Group>
</Group>
<Group key="1425177d-0d0e-4468-8bcc-e638e5509a9b"> <Group key="1425177d-0d0e-4468-8bcc-e638e5509a9b">
<Group key="0"> <Group key="0">
<Data key="Port Format" value="12,6" /> <Data key="Port Format" value="12,6" />

View File

@@ -39,20 +39,6 @@
<build_action v="HEADER;;;;" /> <build_action v="HEADER;;;;" />
<PropertyDeltas /> <PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="crunch.c" persistent="..\lib\common\crunch.c">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="crunch.h" persistent="..\lib\common\crunch.h">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="HEADER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
</dependencies> </dependencies>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089> </CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
@@ -2795,6 +2781,72 @@
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
<filters /> <filters />
</CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0>
<CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3">
<CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2" persistent="">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2">
<dependencies>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2_aliases.h" persistent="Generated_Source\PSoC5\Pin_2_aliases.h">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="HEADER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2.c" persistent="Generated_Source\PSoC5\Pin_2.c">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;CortexM3;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2.h" persistent="Generated_Source\PSoC5\Pin_2.h">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="HEADER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
</dependencies>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
<filters />
</CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0>
<CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3">
<CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1" persistent="">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2">
<dependencies>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1_aliases.h" persistent="Generated_Source\PSoC5\Pin_1_aliases.h">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="HEADER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1.c" persistent="Generated_Source\PSoC5\Pin_1.c">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;CortexM3;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1.h" persistent="Generated_Source\PSoC5\Pin_1.h">
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="HEADER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
</dependencies>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
<filters />
</CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0>
</dependencies> </dependencies>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089> </CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
@@ -3380,6 +3432,6 @@
</ignored_deps> </ignored_deps>
</CyGuid_495451fe-d201-4d01-b22d-5d3f5609ac37> </CyGuid_495451fe-d201-4d01-b22d-5d3f5609ac37>
<boot_component v="" /> <boot_component v="" />
<current_generation v="138" /> <current_generation v="150" />
</CyGuid_fec8f9e8-2365-4bdb-96d3-a4380222e01b> </CyGuid_fec8f9e8-2365-4bdb-96d3-a4380222e01b>
</CyXmlSerializer> </CyXmlSerializer>

View File

@@ -1,7 +1,6 @@
//`#start header` -- edit after this line, do not edit this line //`#start header` -- edit after this line, do not edit this line
`include "cypress.v" `include "cypress.v"
`include "../SuperCounter/SuperCounter.v"
//`#end` -- edit above this line, do not edit this line //`#end` -- edit above this line, do not edit this line
// Generated on 12/11/2019 at 21:18 // Generated on 12/11/2019 at 21:18
@@ -9,7 +8,7 @@
module Sampler ( module Sampler (
output [2:0] debug_state, output [2:0] debug_state,
output reg [7:0] opcode, output reg [7:0] opcode,
output req, output reg req,
input clock, input clock,
input index, input index,
input rdata, input rdata,
@@ -19,90 +18,63 @@ module Sampler (
//`#start body` -- edit after this line, do not edit this line //`#start body` -- edit after this line, do not edit this line
localparam STATE_RESET = 0; // NOTE: Reset pulse is used in both clock domains, and must be long enough
localparam STATE_WAITING = 1; // to be detected in both.
localparam STATE_OPCODE = 2;
reg [1:0] state; reg [5:0] counter;
reg [6:0] counter;
reg oldsampleclock; reg index_q;
wire sampleclocked; reg rdata_q;
assign sampleclocked = !oldsampleclock && sampleclock;
reg oldindex; reg index_edge;
wire indexed; reg rdata_edge;
assign indexed = !oldindex && index;
wire rdataed; reg req_toggle;
reg oldrdata;
assign rdataed = !oldrdata && rdata;
assign req = (state == STATE_OPCODE); always @(posedge sampleclock)
always @(posedge clock)
begin begin
if (reset) if (reset)
begin begin
state <= STATE_RESET; index_edge <= 0;
opcode <= 0; rdata_edge <= 0;
oldsampleclock <= 0; index_q <= 0;
oldindex <= 0; rdata_q <= 0;
oldrdata <= 0;
counter <= 0; counter <= 0;
req_toggle <= 0;
end end
else else
case (state) begin
STATE_RESET: /* Both index and rdata are active high -- positive-going edges
state <= STATE_WAITING; * indicate the start of an index pulse and read pulse, respectively.
*/
STATE_WAITING:
begin index_edge <= index && !index_q;
/* If something has happened, emit any necessary interval byte. */ index_q <= index;
if ((rdataed || indexed) && (counter != 0))
begin rdata_edge <= rdata && !rdata_q;
opcode <= {0, counter}; rdata_q <= rdata;
state <= STATE_OPCODE;
end if (rdata_edge || index_edge || (counter == 6'h3f)) begin
else if (indexed) opcode <= { rdata_edge, index_edge, counter };
begin req_toggle <= ~req_toggle;
oldindex <= 1; counter <= 1; /* remember to count this tick */
opcode <= 8'h81; end else begin
state <= STATE_OPCODE; counter <= counter + 1;
end end
else if (rdataed) end
begin end
oldrdata <= 1;
opcode <= 8'h80; reg req_toggle_q;
state <= STATE_OPCODE;
end always @(posedge clock)
else if (sampleclocked) begin
begin if (reset) begin
oldsampleclock <= 1; req_toggle_q <= 0;
if (counter == 7'h7f) req <= 0;
begin end else begin
opcode <= {0, counter}; req_toggle_q <= req_toggle;
state <= STATE_OPCODE; req <= (req_toggle != req_toggle_q);
end end
counter <= counter + 1;
end
/* Reset state once we've done the thing. */
if (oldrdata && !rdata)
oldrdata <= 0;
if (oldindex && !index)
oldindex <= 0;
if (oldsampleclock && !sampleclock)
oldsampleclock <= 0;
end
STATE_OPCODE: /* opcode or interval byte sent here */
begin
state <= STATE_WAITING;
counter <= 0;
end
endcase
end end
//`#end` -- edit above this line, do not edit this line //`#end` -- edit above this line, do not edit this line

View File

@@ -19,19 +19,15 @@ module Sequencer (
//`#start body` -- edit after this line, do not edit this line //`#start body` -- edit after this line, do not edit this line
localparam STATE_LOAD = 0; localparam STATE_LOAD = 0;
localparam STATE_WAITING = 1; localparam STATE_WRITING = 1;
localparam STATE_PULSING = 2;
localparam STATE_INDEXING = 3;
localparam OPCODE_PULSE = 8'h80; reg state;
localparam OPCODE_INDEX = 8'h81; reg [5:0] countdown;
reg pulsepending;
reg [1:0] state;
reg [6:0] countdown;
assign req = (!reset && (state == STATE_LOAD)); assign req = (!reset && (state == STATE_LOAD));
assign wdata = (state == STATE_PULSING); assign wdata = (!reset && (state == STATE_WRITING) && (countdown == 0) && pulsepending);
assign debug_state = state; assign debug_state = 0;
reg olddataclock; reg olddataclock;
wire dataclocked; wire dataclocked;
@@ -52,49 +48,39 @@ begin
begin begin
state <= STATE_LOAD; state <= STATE_LOAD;
countdown <= 0; countdown <= 0;
pulsepending <= 0;
oldsampleclock <= 0;
end end
else else
begin begin
if (!oldsampleclock && sampleclock) if (!oldsampleclock && sampleclock)
sampleclocked <= 1; sampleclocked <= 1;
oldsampleclock <= sampleclock; oldsampleclock <= sampleclock;
case (state) case (state)
STATE_LOAD: STATE_LOAD:
/* Wait for a posedge on dataclocked, indicating an opcode has begin
/* A posedge on dataclocked indicates that another opcode has
* arrived. */ * arrived. */
if (dataclocked) if (dataclocked)
case (opcode) begin
OPCODE_PULSE: pulsepending <= opcode[7];
state <= STATE_PULSING; countdown <= opcode[5:0] - 1; /* compensate for delay in last tick */
state <= STATE_WRITING;
OPCODE_INDEX: end
state <= STATE_INDEXING; end
default:
begin
countdown <= opcode[6:0];
state <= STATE_WAITING;
end
endcase
STATE_WAITING: STATE_WRITING:
begin
if (sampleclocked) if (sampleclocked)
begin begin
sampleclocked <= 0; if (countdown == 0)
countdown <= countdown - 1;
/* Nasty fudge factor here to account for one to two
* sample ticks lost per pulse. */
if (countdown <= 2)
state <= STATE_LOAD; state <= STATE_LOAD;
else
countdown <= countdown - 1;
sampleclocked <= 0;
end end
end
STATE_PULSING:
state <= STATE_LOAD;
STATE_INDEXING:
if (indexed)
state <= STATE_LOAD;
endcase endcase
end end
end end

View File

Binary file not shown.

View File

@@ -5,7 +5,6 @@
#include <setjmp.h> #include <setjmp.h>
#include "project.h" #include "project.h"
#include "../protocol.h" #include "../protocol.h"
#include "../lib/common/crunch.h"
#define MOTOR_ON_TIME 5000 /* milliseconds */ #define MOTOR_ON_TIME 5000 /* milliseconds */
#define STEP_INTERVAL_TIME 6 /* ms */ #define STEP_INTERVAL_TIME 6 /* ms */
@@ -17,6 +16,9 @@
#define STEP_TOWARDS0 1 #define STEP_TOWARDS0 1
#define STEP_AWAYFROM0 0 #define STEP_AWAYFROM0 0
static bool drive0_present;
static bool drive1_present;
static volatile uint32_t clock = 0; /* ms */ static volatile uint32_t clock = 0; /* ms */
static volatile bool index_irq = false; static volatile bool index_irq = false;
@@ -31,7 +33,6 @@ static struct set_drive_frame current_drive_flags;
static uint8_t td[BUFFER_COUNT]; static uint8_t td[BUFFER_COUNT];
static uint8_t dma_buffer[BUFFER_COUNT][BUFFER_SIZE] __attribute__((aligned())); static uint8_t dma_buffer[BUFFER_COUNT][BUFFER_SIZE] __attribute__((aligned()));
static uint8_t usb_buffer[BUFFER_SIZE] __attribute__((aligned())); static uint8_t usb_buffer[BUFFER_SIZE] __attribute__((aligned()));
static uint8_t xfer_buffer[BUFFER_SIZE] __attribute__((aligned()));
static uint8_t dma_channel; static uint8_t dma_channel;
#define NEXT_BUFFER(b) (((b)+1) % BUFFER_COUNT) #define NEXT_BUFFER(b) (((b)+1) % BUFFER_COUNT)
@@ -42,6 +43,8 @@ static volatile bool dma_underrun = false;
#define DECLARE_REPLY_FRAME(STRUCT, TYPE) \ #define DECLARE_REPLY_FRAME(STRUCT, TYPE) \
STRUCT r = {.f = { .type = TYPE, .size = sizeof(STRUCT) }} STRUCT r = {.f = { .type = TYPE, .size = sizeof(STRUCT) }}
static void stop_motor(void);
static void system_timer_cb(void) static void system_timer_cb(void)
{ {
CyGlobalIntDisable; CyGlobalIntDisable;
@@ -104,7 +107,10 @@ static void print(const char* msg, ...)
static void set_drive_flags(struct set_drive_frame* flags) static void set_drive_flags(struct set_drive_frame* flags)
{ {
if (current_drive_flags.drive != flags->drive) if (current_drive_flags.drive != flags->drive)
{
stop_motor();
homed = false; homed = false;
}
current_drive_flags = *flags; current_drive_flags = *flags;
DRIVESELECT_REG_Write(flags->drive ? 2 : 1); /* select drive 1 or 0 */ DRIVESELECT_REG_Write(flags->drive ? 2 : 1); /* select drive 1 or 0 */
@@ -143,6 +149,12 @@ static void wait_until_writeable(int ep)
; ;
} }
static void wait_until_readable(int ep)
{
while (USBFS_GetEPState(ep) != USBFS_OUT_BUFFER_FULL)
;
}
static void send_reply(struct any_frame* f) static void send_reply(struct any_frame* f)
{ {
print("reply 0x%02x", f->f.type); print("reply 0x%02x", f->f.type);
@@ -160,9 +172,15 @@ static void send_error(int code)
/* buffer must be big enough for a frame */ /* buffer must be big enough for a frame */
static int usb_read(int ep, uint8_t buffer[FRAME_SIZE]) static int usb_read(int ep, uint8_t buffer[FRAME_SIZE])
{ {
if (USBFS_GetEPState(ep) != USBFS_OUT_BUFFER_FULL)
{
USBFS_EnableOutEP(ep);
wait_until_readable(ep);
}
int length = USBFS_GetEPCount(ep); int length = USBFS_GetEPCount(ep);
USBFS_ReadOutEP(ep, buffer, length); USBFS_ReadOutEP(ep, buffer, length);
while (USBFS_GetEPState(ep) == USBFS_OUT_BUFFER_FULL) while (USBFS_GetEPState(ep) != USBFS_OUT_BUFFER_EMPTY)
; ;
return length; return length;
} }
@@ -184,19 +202,19 @@ static void step(int dir)
CyDelay(STEP_INTERVAL_TIME); CyDelay(STEP_INTERVAL_TIME);
} }
static void home(void) /* returns true if it looks like a drive is attached */
static bool home(void)
{ {
for (int i=0; i<100; i++) for (int i=0; i<100; i++)
{ {
/* Don't keep stepping forever, because if a drive's /* Don't keep stepping forever, because if a drive's
* not connected bad things happen. */ * not connected bad things happen. */
if (TRACK0_REG_Read()) if (TRACK0_REG_Read())
break; return true;
step(STEP_TOWARDS0); step(STEP_TOWARDS0);
} }
/* Step to -1, which should be a nop, to reset the disk on disk change. */ return false;
step(STEP_TOWARDS0);
} }
static void seek_to(int track) static void seek_to(int track)
@@ -311,13 +329,7 @@ static void cmd_bulk_read_test(struct any_frame* f)
CyWdtClear(); CyWdtClear();
for (int y=0; y<256; y++) for (int y=0; y<256; y++)
{ {
USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM); usb_read(FLUXENGINE_DATA_OUT_EP_NUM, buffer);
while (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) != USBFS_OUT_BUFFER_FULL)
;
USBFS_ReadOutEP(FLUXENGINE_DATA_OUT_EP_NUM, buffer, sizeof(buffer));
while (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) != USBFS_OUT_BUFFER_EMPTY)
;
for (unsigned z=0; z<sizeof(buffer); z++) for (unsigned z=0; z<sizeof(buffer); z++)
{ {
if (buffer[z] != (uint8)(x+y+z)) if (buffer[z] != (uint8)(x+y+z))
@@ -348,7 +360,7 @@ static void deinit_dma(void)
static void init_capture_dma(void) static void init_capture_dma(void)
{ {
dma_channel = SAMPLER_DMA_DmaInitialize( dma_channel = SAMPLER_DMA_DmaInitialize(
2 /* bytes */, 1 /* bytes */,
true /* request per burst */, true /* request per burst */,
HI16(CYDEV_PERIPH_BASE), HI16(CYDEV_PERIPH_BASE),
HI16(CYDEV_SRAM_BASE)); HI16(CYDEV_SRAM_BASE));
@@ -395,10 +407,6 @@ static void cmd_read(struct read_frame* f)
index_irq = false; index_irq = false;
} }
crunch_state_t cs = {};
cs.outputptr = xfer_buffer;
cs.outputlen = BUFFER_SIZE;
dma_writing_to_td = 0; dma_writing_to_td = 0;
dma_reading_from_td = -1; dma_reading_from_td = -1;
dma_underrun = false; dma_underrun = false;
@@ -455,51 +463,18 @@ static void cmd_read(struct read_frame* f)
{ {
/* Otherwise, there's a block waiting, so attempt to send it. */ /* Otherwise, there's a block waiting, so attempt to send it. */
uint8_t dma_buffer_usage = 0; wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
while (dma_buffer_usage < BUFFER_SIZE) USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, dma_buffer[dma_reading_from_td], BUFFER_SIZE);
{ count++;
cs.inputptr = dma_buffer[dma_reading_from_td] + dma_buffer_usage;
cs.inputlen = BUFFER_SIZE - dma_buffer_usage;
crunch(&cs);
dma_buffer_usage += BUFFER_SIZE - cs.inputlen;
count++;
/* If there is no available space in the output buffer, flush the buffer via
* USB and go again. */
if (cs.outputlen == 0)
{
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
memcpy(usb_buffer, xfer_buffer, FRAME_SIZE);
USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, usb_buffer, BUFFER_SIZE);
cs.outputptr = xfer_buffer;
cs.outputlen = BUFFER_SIZE;
}
}
dma_reading_from_td = NEXT_BUFFER(dma_reading_from_td); dma_reading_from_td = NEXT_BUFFER(dma_reading_from_td);
} }
} }
abort:; abort:;
bool saved_dma_underrun = dma_underrun; bool saved_dma_underrun = dma_underrun;
donecrunch(&cs); /* Terminate the transfer (all transfers are an exact number of fragments). */
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
/* If there's a complete packet waiting, send it. */ USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, NULL, 0);
if (cs.outputlen != BUFFER_SIZE)
{
USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, usb_buffer, BUFFER_SIZE);
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
}
if ((cs.outputlen != 0) && (cs.outputlen != BUFFER_SIZE))
{
/* If there's a partial packet waiting, send it; this will also terminate the transfer. */
USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, usb_buffer, BUFFER_SIZE-cs.outputlen);
}
else
{
/* Otherwise just terminate the transfer. */
USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, NULL, 0);
}
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
deinit_dma(); deinit_dma();
@@ -562,37 +537,18 @@ static void cmd_write(struct write_frame* f)
init_replay_dma(); init_replay_dma();
bool writing = false; /* to the disk */ bool writing = false; /* to the disk */
bool finished = false;
int packets = f->bytes_to_write / FRAME_SIZE; int packets = f->bytes_to_write / FRAME_SIZE;
bool finished = (packets == 0);
int count_written = 0; int count_written = 0;
int count_read = 0; int count_read = 0;
int packetwaiting = 0;
dma_writing_to_td = 0; dma_writing_to_td = 0;
dma_reading_from_td = -1; dma_reading_from_td = -1;
dma_underrun = false; dma_underrun = false;
crunch_state_t cs = {};
cs.outputlen = BUFFER_SIZE;
USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
int old_reading_from_td = -1; int old_reading_from_td = -1;
for (;;) for (;;)
{ {
CyWdtClear(); //CyWdtClear();
/* Make sure that we always have a USB read in progress whenever possible. */
if (!finished && !packetwaiting)
{
/* There is no read in progress; has data arrived in the external USB buffer? */
if (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) == USBFS_OUT_BUFFER_FULL)
{
/* Yes, data has arrived, so initiate the copy. */
packetwaiting = USBFS_ReadOutEP(FLUXENGINE_DATA_OUT_EP_NUM, usb_buffer, FRAME_SIZE);
}
}
/* Read data from USB into the buffers. */ /* Read data from USB into the buffers. */
@@ -601,59 +557,22 @@ static void cmd_write(struct write_frame* f)
if (writing && (dma_underrun || index_irq)) if (writing && (dma_underrun || index_irq))
goto abort; goto abort;
/* Read crunched data, if necessary. */ uint8_t* buffer = dma_buffer[dma_writing_to_td];
if (finished)
if (cs.inputlen == 0)
{ {
if (finished) /* There's no more data to read, so fake some. */
{
/* There's no more data to read, so fake some. */ memset(buffer, 0x3f, BUFFER_SIZE);
for (int i=0; i<BUFFER_SIZE; i++)
xfer_buffer[i+0] = 0x7f;
cs.inputptr = xfer_buffer;
cs.inputlen = BUFFER_SIZE;
}
else if (packetwaiting)
{
/* There's a USB read into usb_buffer in progress, so check if it's finished. */
if (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) == USBFS_OUT_BUFFER_EMPTY)
{
/* It's done, so copy out the data. */
memcpy(xfer_buffer, usb_buffer, FRAME_SIZE);
cs.inputptr = xfer_buffer;
cs.inputlen = packetwaiting;
count_read++;
if ((packetwaiting < FRAME_SIZE) || (count_read == packets))
finished = true;
else
{
/* Wait for more USB data to show up. */
packetwaiting = 0;
USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
}
}
}
} }
else
/* If there *is* data waiting in the buffer, uncrunch it. */
if (cs.inputlen != 0)
{ {
cs.outputptr = dma_buffer[dma_writing_to_td] + BUFFER_SIZE - cs.outputlen; (void) usb_read(FLUXENGINE_DATA_OUT_EP_NUM, buffer);
uncrunch(&cs); count_read++;
if (cs.outputlen == 0)
{ if (count_read == packets)
/* Completed a DMA buffer; queue it for writing. */ finished = true;
dma_writing_to_td = NEXT_BUFFER(dma_writing_to_td);
cs.outputlen = BUFFER_SIZE;
}
} }
dma_writing_to_td = NEXT_BUFFER(dma_writing_to_td);
/* Once all the buffers are full, start writing. */ /* Once all the buffers are full, start writing. */
@@ -693,7 +612,6 @@ static void cmd_write(struct write_frame* f)
} }
} }
abort: abort:
print("done %d %d", dma_reading_from_td, dma_writing_to_td);
SEQUENCER_DMA_FINISHED_IRQ_Disable(); SEQUENCER_DMA_FINISHED_IRQ_Disable();
SEQUENCER_CONTROL_Write(1); /* reset */ SEQUENCER_CONTROL_Write(1); /* reset */
@@ -713,27 +631,21 @@ abort:
* easier than trying to terminate the connection. */ * easier than trying to terminate the connection. */
while (count_read != packets) while (count_read != packets)
{ {
if (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) == USBFS_OUT_BUFFER_FULL) (void) usb_read(FLUXENGINE_DATA_OUT_EP_NUM, usb_buffer);
{ count_read++;
int length = usb_read(FLUXENGINE_DATA_OUT_EP_NUM, usb_buffer);
if (length < FRAME_SIZE)
break;
USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
count_read++;
}
} }
USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
} }
deinit_dma(); deinit_dma();
print("write finished");
if (dma_underrun) if (dma_underrun)
{ {
print("underrun!");
send_error(F_ERROR_UNDERRUN); send_error(F_ERROR_UNDERRUN);
return; return;
} }
print("success");
DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_WRITE_REPLY); DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_WRITE_REPLY);
send_reply((struct any_frame*) &r); send_reply((struct any_frame*) &r);
} }
@@ -761,6 +673,10 @@ static void cmd_erase(struct erase_frame* f)
static void cmd_set_drive(struct set_drive_frame* f) static void cmd_set_drive(struct set_drive_frame* f)
{ {
if (drive0_present && !drive1_present)
f->drive = 0;
if (drive1_present && !drive0_present)
f->drive = 1;
set_drive_flags(f); set_drive_flags(f);
DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_SET_DRIVE_REPLY); DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_SET_DRIVE_REPLY);
@@ -914,6 +830,21 @@ static void handle_command(void)
} }
} }
static void detect_drives(void)
{
current_drive_flags.drive = 0;
start_motor();
drive0_present = home();
stop_motor();
current_drive_flags.drive = 1;
start_motor();
drive1_present = home();
stop_motor();
print("drive 0: %s drive 1: %s", drive0_present ? "yes" : "no", drive1_present ? "yes" : "no");
}
int main(void) int main(void)
{ {
CyGlobalIntEnable; CyGlobalIntEnable;
@@ -927,11 +858,11 @@ int main(void)
DRIVESELECT_REG_Write(0); DRIVESELECT_REG_Write(0);
UART_Start(); UART_Start();
USBFS_Start(0, USBFS_DWR_VDDD_OPERATION); USBFS_Start(0, USBFS_DWR_VDDD_OPERATION);
USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
detect_drives();
CyWdtStart(CYWDT_1024_TICKS, CYWDT_LPMODE_DISABLED); CyWdtStart(CYWDT_1024_TICKS, CYWDT_LPMODE_DISABLED);
/* UART_PutString("GO\r"); */
for (;;) for (;;)
{ {
CyWdtClear(); CyWdtClear();
@@ -947,7 +878,7 @@ int main(void)
{ {
print("Waiting for USB..."); print("Waiting for USB...");
while (!USBFS_GetConfiguration()) while (!USBFS_GetConfiguration())
; CyWdtClear();
print("USB ready"); print("USB ready");
USBFS_EnableOutEP(FLUXENGINE_CMD_OUT_EP_NUM); USBFS_EnableOutEP(FLUXENGINE_CMD_OUT_EP_NUM);
} }

View File

@@ -39,7 +39,7 @@ CFLAGS += -Ilib -Idep/fmt -Iarch
export OBJDIR = .obj export OBJDIR = .obj
all: .obj/build.ninja all: .obj/build.ninja
@ninja -f .obj/build.ninja -v @ninja -f .obj/build.ninja
clean: clean:
@echo CLEAN @echo CLEAN

View File

@@ -24,17 +24,10 @@ Don't believe me? Watch the demo reel!
<iframe width="373" height="210" src="https://www.youtube.com/embed/m_s1iw8eW7o" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <iframe width="373" height="210" src="https://www.youtube.com/embed/m_s1iw8eW7o" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div> </div>
**Important note.** On 2019-02-09 I did a hardware redesign and moved the pins on **Important note.** On 2020-04-02 I changed the bytecode format (and firmware).
the board. Sorry for the inconvenience, but it means you don't have to modify Flux files will need to be upgraded with `fluxengine upgradefluxfile`. The new
the board any more to make it work. If you built the hardware prior to then, format should be more reliable and use way, way less bandwidth. Sorry for the
you'll need to adjust it. inconvenience.
**Another important note.** On 2019-07-03 I've revamped the build process and
the (command line) user interface. It should be much nicer now, not least in
that there's a single client binary with all the functionality in it. The
interface is a little different, but not much. The build process is now
better (simpler). See [the building](doc/building.md) and
[using](doc/using.md) pages for more details.
Where? Where?
------ ------

View File

@@ -20,6 +20,8 @@ public:
RecordType advanceToNextRecord(); RecordType advanceToNextRecord();
void decodeSectorRecord(); void decodeSectorRecord();
std::set<unsigned> requiredSectors(Track& track) const;
}; };
class AmigaEncoder : public AbstractEncoder class AmigaEncoder : public AbstractEncoder

View File

@@ -32,6 +32,8 @@ AbstractDecoder::RecordType AmigaDecoder::advanceToNextRecord()
void AmigaDecoder::decodeSectorRecord() void AmigaDecoder::decodeSectorRecord()
{ {
const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16); 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& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE); const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE);
@@ -56,3 +58,10 @@ void AmigaDecoder::decodeSectorRecord()
_sector->data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo); _sector->data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM; _sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
} }
std::set<unsigned> AmigaDecoder::requiredSectors(Track& track) const
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
return sectors;
}

View File

@@ -9,7 +9,8 @@
#define BROTHER_DATA_RECORD_CHECKSUM 3 #define BROTHER_DATA_RECORD_CHECKSUM 3
#define BROTHER_DATA_RECORD_ENCODED_SIZE 415 #define BROTHER_DATA_RECORD_ENCODED_SIZE 415
#define BROTHER_TRACKS_PER_DISK 78 #define BROTHER_TRACKS_PER_240KB_DISK 78
#define BROTHER_TRACKS_PER_120KB_DISK 39
#define BROTHER_SECTORS_PER_TRACK 12 #define BROTHER_SECTORS_PER_TRACK 12
class Sector; class Sector;
@@ -28,8 +29,16 @@ public:
class BrotherEncoder : public AbstractEncoder class BrotherEncoder : public AbstractEncoder
{ {
public: public:
BrotherEncoder(int format, int bias):
_format(format),
_bias(bias)
{}
virtual ~BrotherEncoder() {} virtual ~BrotherEncoder() {}
private:
int _format;
int _bias;
public: public:
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors); std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
}; };

View File

@@ -129,9 +129,25 @@ static int charToInt(char c)
std::unique_ptr<Fluxmap> BrotherEncoder::encode( std::unique_ptr<Fluxmap> BrotherEncoder::encode(
int physicalTrack, int physicalSide, const SectorSet& allSectors) int physicalTrack, int physicalSide, const SectorSet& allSectors)
{ {
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_DISK) int logicalTrack;
|| (physicalSide != 0)) if (physicalSide != 0)
return std::unique_ptr<Fluxmap>(); return std::unique_ptr<Fluxmap>();
physicalTrack -= _bias;
switch (_format)
{
case 120:
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|| (physicalTrack & 1))
return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack/2;
break;
case 240:
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack;
break;
}
int bitsPerRevolution = 200000.0 / clockRateUs; int bitsPerRevolution = 200000.0 / clockRateUs;
const std::string& skew = sectorSkew.get(); const std::string& skew = sectorSkew.get();
@@ -146,10 +162,10 @@ std::unique_ptr<Fluxmap> BrotherEncoder::encode(
double dataMs = headerMs + postHeaderSpacingMs; double dataMs = headerMs + postHeaderSpacingMs;
unsigned dataCursor = dataMs*1e3 / clockRateUs; unsigned dataCursor = dataMs*1e3 / clockRateUs;
const auto& sectorData = allSectors.get(physicalTrack, 0, sectorId); const auto& sectorData = allSectors.get(logicalTrack, 0, sectorId);
fillBitmapTo(bits, cursor, headerCursor, { true, false }); fillBitmapTo(bits, cursor, headerCursor, { true, false });
write_sector_header(bits, cursor, physicalTrack, sectorId); write_sector_header(bits, cursor, logicalTrack, sectorId);
fillBitmapTo(bits, cursor, dataCursor, { true, false }); fillBitmapTo(bits, cursor, dataCursor, { true, false });
write_sector_data(bits, cursor, sectorData->data); write_sector_data(bits, cursor, sectorData->data);
} }

View File

@@ -57,11 +57,11 @@ const FluxPattern FM_TRS80DAM1_PATTERN(16, 0xf56b);
/* /*
* TRS80DAM2 record: * TRS80DAM2 record:
* flux: XXXX-X-X-XX-XXX- = 0xf56c * flux: XXXX-X-X-XX-XXX- = 0xf56e
* clock: X X - - - X X X = 0xc7 * clock: X X - - - X X X = 0xc7
* data: X X X X X - X - = 0xfa * data: X X X X X - X - = 0xfa
*/ */
const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56c); const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56e);
/* MFM record separator: /* MFM record separator:
* 0xA1 is: * 0xA1 is:

View File

@@ -205,6 +205,7 @@ std::unique_ptr<Fluxmap> IbmEncoder::encode(
Bytes truncatedData = sectorData->data.slice(0, _parameters.sectorSize); Bytes truncatedData = sectorData->data.slice(0, _parameters.sectorSize);
bw += truncatedData; bw += truncatedData;
hexdump(std::cout, data.slice(0, 64));
uint16_t crc = crc16(CCITT_POLY, data); uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc); bw.write_be16(crc);

View File

@@ -32,18 +32,24 @@ struct IbmIdam
class IbmDecoder : public AbstractDecoder class IbmDecoder : public AbstractDecoder
{ {
public: public:
IbmDecoder(unsigned sectorBase, bool ignoreSideByte=false): IbmDecoder(unsigned sectorBase, bool ignoreSideByte=false,
const std::set<unsigned> requiredSectors=std::set<unsigned>()):
_sectorBase(sectorBase), _sectorBase(sectorBase),
_ignoreSideByte(ignoreSideByte) _ignoreSideByte(ignoreSideByte),
_requiredSectors(requiredSectors)
{} {}
RecordType advanceToNextRecord(); RecordType advanceToNextRecord();
void decodeSectorRecord(); void decodeSectorRecord();
void decodeDataRecord(); void decodeDataRecord();
std::set<unsigned> requiredSectors(Track& track) const
{ return _requiredSectors; }
private: private:
unsigned _sectorBase; unsigned _sectorBase;
bool _ignoreSideByte; bool _ignoreSideByte;
std::set<unsigned> _requiredSectors;
unsigned _currentSectorSize; unsigned _currentSectorSize;
unsigned _currentHeaderLength; unsigned _currentHeaderLength;
}; };

View File

@@ -121,7 +121,7 @@ uint8_t decode_side(uint8_t side)
* bit 5) and also whether we're above track 0x3f (in bit 6). * bit 5) and also whether we're above track 0x3f (in bit 6).
*/ */
return !!(side & 0x40); return !!(side & 0x20);
} }
AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord() AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord()
@@ -184,3 +184,25 @@ void MacintoshDecoder::decodeDataRecord()
_sector->data.clear(); _sector->data.clear();
_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12)); _sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12));
} }
std::set<unsigned> MacintoshDecoder::requiredSectors(Track& track) const
{
int count;
if (track.physicalTrack < 16)
count = 12;
else if (track.physicalTrack < 32)
count = 11;
else if (track.physicalTrack < 48)
count = 10;
else if (track.physicalTrack < 64)
count = 9;
else
count = 8;
std::set<unsigned> sectors;
while (count--)
sectors.insert(count);
return sectors;
}

View File

@@ -18,6 +18,8 @@ public:
RecordType advanceToNextRecord(); RecordType advanceToNextRecord();
void decodeSectorRecord(); void decodeSectorRecord();
void decodeDataRecord(); void decodeDataRecord();
std::set<unsigned> requiredSectors(Track& track) const;
}; };
#endif #endif

View File

@@ -37,7 +37,7 @@ AbstractDecoder::RecordType MxDecoder::advanceToNextRecord()
const FluxMatcher* matcher = nullptr; const FluxMatcher* matcher = nullptr;
_sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher); _sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher);
readRawBits(32); /* skip the ID mark */ readRawBits(32); /* skip the ID mark */
_logicalTrack = decodeFmMfm(readRawBits(32)).reader().read_be16(); _logicalTrack = decodeFmMfm(readRawBits(32)).slice(0, 32).reader().read_be16();
} }
else if (_currentSector == 10) else if (_currentSector == 10)
{ {

View File

@@ -25,6 +25,8 @@ This is the physical stuff you'll need.
connector](https://eu.mouser.com/ProductDetail/Amphenol-FCI/86130342114345E1LF?qs=%2Fha2pyFadug%252BpMTyxmFhglPPVKuWXYuFpPNgq%252BsrzhDnXxo8B28k7UCGc7F%2FXjsi) connector](https://eu.mouser.com/ProductDetail/Amphenol-FCI/86130342114345E1LF?qs=%2Fha2pyFadug%252BpMTyxmFhglPPVKuWXYuFpPNgq%252BsrzhDnXxo8B28k7UCGc7F%2FXjsi)
(or one of the other myriad compatible connectors; there's a billion). (or one of the other myriad compatible connectors; there's a billion).
- A floppy drive cable, preferably one with two connectors and a twist.
- A suitable power supply. 3.5" floppy drives use 5V at about an amp - A suitable power supply. 3.5" floppy drives use 5V at about an amp
(usually less) --- sadly, too much to power from USB. 5.25" floppy drives (usually less) --- sadly, too much to power from USB. 5.25" floppy drives
also require 12V. An old but decent quality PC power supply is ideal, as also require 12V. An old but decent quality PC power supply is ideal, as
@@ -48,7 +50,7 @@ All you need to do is attach your chosen connector to the board. You'll need
to make sure that pin 2 on the cable is connected to pin 2.7 on the board, to make sure that pin 2 on the cable is connected to pin 2.7 on the board,
and pin 34 to pin 1.7 on the board (and of course all the ones in between). and pin 34 to pin 1.7 on the board (and of course all the ones in between).
Apart from grounding the board (see below), this is literally all there is to Apart from grounding the board (see below), this is literally all there is to
it. it. The actual pinout is described in detail below.
The pads are small, but soldering them isn't too bad with a needle-nosed The pads are small, but soldering them isn't too bad with a needle-nosed
soldering iron tip. soldering iron tip.
@@ -173,6 +175,7 @@ pattern. Press and hold the little button near the light for five seconds
until the light stays solidly on. Now you should be able to acquire until the light stays solidly on. Now you should be able to acquire
the port and proceed normally. the port and proceed normally.
## Building the client ## Building the client
The client software is where the intelligence, such as it is, is. It's pretty The client software is where the intelligence, such as it is, is. It's pretty
@@ -181,12 +184,12 @@ well, although on Windows it'll need MSYS2 and mingw32. You'll need to
install some support packages. install some support packages.
- For Linux (this is Ubuntu, but this should apply to Debian too): - For Linux (this is Ubuntu, but this should apply to Debian too):
`ninja-build`, `libusb-1.0-0-dev`, `libsqlite3-dev`. `ninja-build`, `libusb-1.0-0-dev`, `libsqlite3-dev`.
- For OSX with Homebrew: `ninja`, `libusb`, `pkg-config`, `sqlite`. - For OSX with Homebrew: `ninja`, `libusb`, `pkg-config`, `sqlite`.
- For Windows with MSYS2: `make`, `ninja`, `mingw-w64-i686-libusb`, - For Windows with MSYS2: `make`, `ninja`, `mingw-w64-i686-libusb`,
`mingw-w64-i686-sqlite3`, `mingw-w64-i686-zlib`, `mingw-w64-i686-gcc`. `mingw-w64-i686-sqlite3`, `mingw-w64-i686-zlib`, `mingw-w64-i686-gcc`.
These lists are not necessarily exhaustive --- plaese [get in These lists are not necessarily exhaustive --- please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new) if I've missed touch](https://github.com/davidgiven/fluxengine/issues/new) if I've missed
anything. anything.
@@ -197,11 +200,176 @@ dependencies and you should be able to put it anywhere.
If it doesn't build, please [get in If it doesn't build, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new). touch](https://github.com/davidgiven/fluxengine/issues/new).
## Connecting it up
You should now have a working board, so it's time to test it.
1. Plug the motherboard end of your floppy disk cable into the FluxEngine.
The **red stripe goes on the right**. The **lower set of
holes connect to the board**. See the pinout below.
If you're using header pins, the upper row of holes in the connector
should overhang the edge of the board. If you're using a floppy drive
motherboard connector, you're golden, of course (unless you have one of
those annoying unkeyed cables, or have accidentally soldered the
connector on in the wrong place --- don't laugh, I've done it.)
2. Plug the drive end of your floppy disk cable into the drive (or drives).
Floppy disk cables typically have [two pairs of floppy disk drive
connectors with a twist between
them](http://www.nullmodem.com/Floppy.htm). (Each pair has one connector
for a 3.5" drive and a different one for a 5.25" drive.) (Some cables
are cheap and just have the 3.5" connectors. Some are _very_ cheap and
have a single 3.5" connector, after the twist.)
If you have **two** drives, plug them into both connectors. FluxEngine,
sadly, non-standard disk numbering (there are reasons). Drive 0 is the
one nearest the motherboard; that is, before the twist. Drive 1 is the
one at the end of the cable; that is, after the twist. Drive 0 is the
default. You can tell the client to select drive 1 by using `-s :d=1`.
If you have **one** drive, you may plug it into _either_ connector.
FluxEngine will autodetect it and treat it as drive 0. However, you'll
get the most reliable electrical signal if you plug it in at the end of
the cable.
**A note on termination:** some 5.25" drives require jumper configuration
to tell them whether they're at the end of the cable or in the middle of
the cable. 3.5" drives don't, and my 5.25" drives don't, so I can't
advise there. Consult your drive datasheet for details.
3. **Important.** Make sure that no disk you care about is in the drive.
(Because if your wiring is wrong and a disk is inserted, you'll corrupt
it.)
4. Connect the floppy drive to power. Nothing should happen. If you've
connected something in backwards, you'll see the drive light up, the motor
start, and if you didn't take the disk out, one track has just been wiped.
If this happens, check your wiring.
5. Strip off the little piece of protective plastic on the USB socket on the
board --- the little socket at the end, not the big programmer plug.
6. Connect the FluxEngine to your PC via USB.
7. Insert a scratch disk and do `fluxengine rpm` from the shell. The motor
should work and it'll tell you that the disk is spinning at about 300
rpm for a 3.5" disk, or 360 rpm for a 5.25" disk. If it doesn't, please
[get in touch](https://github.com/davidgiven/fluxengine/issues/new).
8. Do `fluxengine test bandwidth` from the shell. It'll measure your USB
bandwidth. Ideally you should be getting above 800kB/s in both directions.
FluxEngine needs about 300kB/s for a DD disk and about 600kB/s for a HD
disk, so if you're getting less than this, try a different USB port.
9. Insert a standard PC formatted floppy disk into the drive (probably a good
idea to remove the old disk first). Then do `fluxengine read ibm`. It
should read the disk, emitting copious diagnostics, and spit out an
`ibm.img` file containing the decoded disk image (either 1440kB or 720kB
depending).
10. Profit!
## Technical details
The board pinout and the way it's connected to the floppy bus is described
below.
```ditaa
:-E -s 0.75
+-----+
|||||||
+----+-----+----+
+cAAA +
+ Debug board +
+----+-----+----+
+ GND|cDDD | VDD+
+----+ +----+
INDEX300 ---+ 3.0| | GND+--------------------------+
+----+ +----+ +--+--+ |
INDEX360 ---+ 3.1| | 1.7+------ DISKCHG --+34+33+--+
+----+ +----+ +--+--+
+ 3.2| | 1.6+------- SIDE1 ---+32+31+
+----+ +----+ +--+--+
+ 3.3| | 1.5+------- RDATA ---+30+29+
+----+ +----+ +--+--+
+ 3.4| | 1.4+-------- WPT ----+28+27+
+----+ +----+ +--+--+
+ 3.5| | 1.3+------- TRK00 ---+26+25+
+----+ +----+ +--+--+
+ 3.6| | 1.2+------- WGATE ---+24+23+
+----+ +----+ +--+--+
+ 3.7| | 1.1+------- WDATA ---+22+21+
+----+ +----+ +--+--+
+15.0| | 1.0+------- STEP ----+20+19+
+----+ +----+ +--+--+
+15.1| |12.0+-------- DIR ----+18+17+
+----+ +----+ +--+--+
+15.2| |12.1+------- MOTEB ---+16+15+
+----+ +----+ +--+--+
+15.3| |12.2+------- DRVSA ---+14+13+
+----+ +----+ +--+--+
+15.4| |12.3+------- DRVSB ---+12+11+
+----+ +----+ +--+--+
+15.5| |12.4+------- MOTEA ---+10+9 +
+----+ +----+ +--+--+
+ 0.0| |12.5+------- INDEX ---+8 +7 +
+----+ +----+ +--+--+
+ 0.1| |12.6+-------- n/c ----+6 +5 +
+----+ +----+ +--+--+
+ 0.2| |12.7+- TX --- n/c ----+4 +3 +
+----+ +----+ +--+--+
+ 0.3| | 2.7+------- REDWC ---+2 +1 +
+----+ +----+ +--+--+
+ 0.4| | 2.6+
+----+ +----+ FDD socket
+ 0.5| | 2.5+
+----+ +----+
+ 0.6| | 2.4+ TX: debug UART from board
+----+ +----+
+ 0.7| | 2.3+
+----+ +----+
+ RST| | 2.2+
+----+ +----+
+ GND| | 2.1+
+----+ USB +----+
+ VDD+-----+ 2.0+
+----+-----+----+
PSoC5 board
```
Notes:
- `TX` is the debug UART port. It's on pin 12.7 because the board routes it
to the USB serial port on the programmer, so you can get debug information
from the FluxEngine by just plugging the programming end into a USB port
and using a serial terminal at 115200 baud. If you solder a floppy drive
connector on, then it'll end up connected to pin 4 of the floppy drive bus,
which is usually not connected. It's possible that some floppy drives do,
in fact, use this pin. You may wish to remove pin 4 from the floppy drive
socket before attaching it to the FluxEngine to make sure that this pin is
not connected; however, so far I have not found any drives for which this
is necessary. If you do find one, _please_ [get in
touch](https://github.com/davidgiven/fluxengine/issues/new) so I can
document it.
- The `GND` pin only really needs to be connected to one of the floppy bus
ground pins; pin 33 is the closest. For extra safety, you can bridge all
the odd numbered pins together and ground them all if you like.
- `INDEX300` and `INDEX360` are optional output pins which generate fake
timing pulses for 300 and 360 RPM drives. These are useful for certain
rather exotic things. See the section on flippy disks [in the FAQ](faq.md)
for more details; you can normally ignore these.
## Next steps ## Next steps
The board's now assembled and programmed. Plug it into your drive, strip the You should now be ready to go. You'll want to read [the client
plastic off the little USB connector and plug that into your computer, and documentation](using.md) for information about how to actually do interesting
you're ready to start using it. things.
I _do_ make updates to the firmware whenever necessary, so you may need to I _do_ make updates to the firmware whenever necessary, so you may need to
reprogram it at intervals; you may want to take this into account if you reprogram it at intervals; you may want to take this into account if you

View File

@@ -14,10 +14,12 @@ Apparently about 20% of Brother word processors have alignment issues which
means that the disks can't be read by FluxEngine (because the tracks on the means that the disks can't be read by FluxEngine (because the tracks on the
disk don't line up with the position of the head in a PC drive). The word disk don't line up with the position of the head in a PC drive). The word
processors themselves solved this by microstepping until they found where the processors themselves solved this by microstepping until they found where the
real track is, but normal PC drives aren't capable of doing this. real track is, but normal PC drives aren't capable of doing this. Particularly
Particularly with the 120kB disks, you might want to fiddle with the start with the 120kB disks, you might want to fiddle with the start track (e.g.
track (e.g. `:t=0-79x2`) to get a clean read. Keep an eye on the bad sector `:t=0-79x2`) to get a clean read. Keep an eye on the bad sector map that's
map that's dumped at the end of a read. dumped at the end of a read. My word processor likes to put logical track 0 on
physical track 3, which means that logical track 77 is on physical track 80;
luckily my PC drive can access track 80.
Using FluxEngine to *write* disks isn't a problem, so the Using FluxEngine to *write* disks isn't a problem, so the
simplest solution is to use FluxEngine to create a new disk, with the tracks simplest solution is to use FluxEngine to create a new disk, with the tracks
@@ -30,7 +32,7 @@ If you find one of these misaligned disks then *please* [get in
touch](https://github.com/davidgiven/fluxengine/issues/new); I want to touch](https://github.com/davidgiven/fluxengine/issues/new); I want to
investigate. investigate.
Reading discs Reading disks
------------- -------------
Just do: Just do:
@@ -41,7 +43,7 @@ fluxengine read brother
You should end up with a `brother.img` which is 239616 bytes long. You should end up with a `brother.img` which is 239616 bytes long.
Writing discs Writing disks
------------- -------------
Just do: Just do:
@@ -53,6 +55,27 @@ fluxengine write brother
...and it'll write a `brother.img` file which is 239616 bytes long to the ...and it'll write a `brother.img` file which is 239616 bytes long to the
disk. (Use `-i` to specify a different input filename.) disk. (Use `-i` to specify a different input filename.)
Dealing with misaligned disks
-----------------------------
While FluxEngine can't read misaligned disks directly, Brother word processors
_can_. If you have access to a compatible word processor, there's a fairly
simple workaround to allow you to extract the data:
1. Format a disk using FluxEngine (by simply writing a blank filesystem image
to a disk). This will have the correct alignment to work on a PC drive.
2. Use a word processor to copy the misaligned disk to the newly formatted
disk. The machine will happily adjust itself to both sets of alignments.
3. Use FluxEngine to read the data off the correctly aligned disk.
I realise this is rather unsatisfactory, as the Brother hardware is becoming
rarer and they cope rather badly with damaged disks, but this is a limitation
of the hardware of normal PC drives. (It _is_ possible to deliberately misalign
a drive to make it match up with a bad disk, but this is for experts only --- I
wouldn't dare.)
Low level format Low level format
---------------- ----------------
@@ -60,14 +83,6 @@ The drive is a single-sided 3.5" drive spinning at not 300 rpm (I don't know
the precise speed yet but FluxEngine doesn't care). The 240kB disks have 78 the precise speed yet but FluxEngine doesn't care). The 240kB disks have 78
tracks and the 120kB disks have 39. tracks and the 120kB disks have 39.
The Brother drive alignment is kinda variable; when you put the disk in the
drive it seeks all the way to physical track 0 and then starts searching for
something which looks like data. My machine likes to put logical track 0 on
physical track 3. FluxEngine puts logical track 0 on physical track 0 for
simplicity, which works fine (at least on my machine). If this doesn't work
for you, [get in touch](https://github.com/davidgiven/fluxengine/issues/new);
there are potential workarounds.
Each track has 12 256-byte sectors. The drive ignores the index hole so they're Each track has 12 256-byte sectors. The drive ignores the index hole so they're
lined up all anyhow. As FluxEngine can only read from index to index, it lined up all anyhow. As FluxEngine can only read from index to index, it
actually reads two complete revolutions and reassembles the sectors from that. actually reads two complete revolutions and reassembles the sectors from that.
@@ -138,7 +153,8 @@ mcopy -i brother.img ::brother.doc linux.doc
``` ```
The word processor checks the media byte, unfortunately, so you'll need to The word processor checks the media byte, unfortunately, so you'll need to
change it back to 0x58 before writing an image to disk. change it back to 0x58 before writing an image to disk. Just run
`brother240tool` on the image again and it will flip it back.
The file format is not WP-1, and currently remains completely unknown, The file format is not WP-1, and currently remains completely unknown,
although it's probably related. If anyone knows anything about this, please although it's probably related. If anyone knows anything about this, please

View File

@@ -41,19 +41,28 @@ of the disk image will vary depending on the format.
Configuration options you'll want include: Configuration options you'll want include:
- `--sector-id-base`: specifies the ID of the first sector; this defaults - `--ibm-sector-id-base=N`: specifies the ID of the first sector; this defaults
to 1. Some formats (like the Acorn ones) start at 0. This can't be to 1. Some formats (like the Acorn ones) start at 0. This can't be
autodetected because FluxEngine can't distinguish between a disk which autodetected because FluxEngine can't distinguish between a disk which
starts at sector 1 and a disk which starts at sector 0 but all the sector starts at sector 1 and a disk which starts at sector 0 but all the sector
0s are missing. 0s are missing.
- `--ignore-side-byte`: each sector header describes the location of the - `--ibm-ignore-side-byte=true|false`: each sector header describes the location of the
sector: sector ID, track and side. Some formats use the wrong side ID, so sector: sector ID, track and side. Some formats use the wrong side ID, so
the sectors on side 1 are labelled as belonging to side 0. This causes the sectors on side 1 are labelled as belonging to side 0. This causes
FluxEngine to see duplicate sectors (as it can't distinguish between the FluxEngine to see duplicate sectors (as it can't distinguish between the
two sides). This option tells FluxEngine to ignore the side byte completely two sides). This option tells FluxEngine to ignore the side byte completely
and use the physical side instead. and use the physical side instead.
- `--ibm-required-sectors=range`: if you know how many sectors to expect per
track, you can improve reads by telling FluxEngine what to expect here. If
a track is read and a sector on this list is _not_ present, then FluxEngine
assumes the read failed and will retry. This avoids the situation where
FluxEngine can't tell the difference between a sector missing because it's
bad or a sector missing because it was never written in the first place. If
sectors are seen outside the range here, it will still be read. You can use
the same syntax as for track specifiers: e.g. `0-9`, `0,1,2,3`, etc.
Writing disks Writing disks
------------- -------------

View File

@@ -45,22 +45,20 @@ Reading discs
Just do: Just do:
``` ```
fluxengine read mac fluxengine read mac -o mac.diskcopy
``` ```
You should end up with an `mac.img` which is 1001888 bytes long (for a normal You should end up with a `mac.diskcopy` file which is compatible with DiskCopy
DD disk). If you want the single-sided variety, use `-s :s=0`. 4.2, which most Mac emulators support.
**Big warning!** The image may not work in an emulator. Mac disk images are **Big warning!** Mac disk images are complicated due to the way the tracks are
complicated due to the way the tracks are different sizes and the odd sector different sizes and the odd sector size. If you use a normal `.img` file, then
size. FluxEngine chooses to store them in a simple 524 x 12 x 2 x 80 layout, FluxEngine will store them in a simple 524 x 12 x 2 x 80 layout, with holes
with holes where missing sectors should be. This was easiest. If anyone can where missing sectors should be; this was easiest, but is unlikely to work with
suggest a better way, please [get in most Mac emulators and other software. In these files, the The 12 bytes of
touch](https://github.com/davidgiven/fluxengine/issues/new). metadata _follow_ the 512 bytes of user payload in the sector image. If you
don't want it, specify a geometry in the output file with a 512-byte sectore
The 12 bytes of metadata _follow_ the 512 bytes of user payload in the sector size like `-o mac.img:c=80:h=1:s=12:b=512`.
image. If you don't want it, specify a geometry in the output file with a
512-byte sectore size like `-o mac.img:c=80:h=1:s=12:b=512`.
Useful references Useful references
----------------- -----------------
@@ -74,3 +72,7 @@ Useful references
- [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an - [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an
epicly detailed writeup of the Apple II disk format (which is closely related) epicly detailed writeup of the Apple II disk format (which is closely related)
- [The DiskCopy 4.2
format](https://www.discferret.com/wiki/Apple_DiskCopy_4.2), described on
the DiskFerret website.

View File

@@ -22,19 +22,17 @@ FluxEngine, where a different datapath state machine thingy (the PSoC5LP has
24, all independently programmable) to interpret the bytecodes, generate a 24, all independently programmable) to interpret the bytecodes, generate a
stream of pulses to the disk. stream of pulses to the disk.
The bytecode format represents an interval between pulses as a byte, a pulse The bytecode format is very simple with a six-bit interval since the previous
as a byte, and the index hole as a byte. Timer overflows are handled by event in the lower six bits and the top two bits are set of a pulse or an index
sending multiple intervals in a row. However, the USB transport applies a hole (or both, or neither).
simple compression system to this in order to get the USB bandwidth down to
something manageable.
An HD floppy has a nominal pulse frequency of 500kHz, and we use a sample An HD floppy has a nominal pulse frequency of 500kHz, and we use a sample
clock of 12MHz (every 83ns). This means that our 500kHz pulses will have an clock of 12MHz (every 83ns). This means that our 500kHz pulses will have an
average interval of 24. This gives us more than enough resolution. At this average interval of 24. This gives us more than enough resolution. At this
speed, in the 200ms that a 3.5" disk takes to rotate, we will see about speed, in the 200ms that a 3.5" disk takes to rotate, we will see about
100,000 pulses. Each one is encoded as two bytes, one for the interval and 100,000 pulses. Each one is encoded as a single byte; so that revolution
one to generate the pulse; so that revolution generates 200kB of data. generates 100kB of data. (Extremely approximately. The actual figure varies
(Extremely approximately. The actual figure is less.) depending on what data is stored on the disk.)
(The clock needs to be absolutely rock solid or we get jitter which makes the (The clock needs to be absolutely rock solid or we get jitter which makes the
data difficult to analyse, so 12 was chosen to be derivable from the data difficult to analyse, so 12 was chosen to be derivable from the

View File

@@ -1,78 +1,8 @@
Using a FluxEngine Using a FluxEngine
================== ==================
So you've [built the hardware](building.md)! What now? So you've [built the hardware](building.md), programmed and tested it! What
now?
## Connecting it up
In order to do anything useful, you have to plug it in to a floppy disk drive (or two).
1. Plug the motherboard end of your floppy disk cable into the FluxEngine.
The **red stripe goes on the right**. The **lower set of
holes connect to the board**. (Pin 2 of the connector needs to connect
to pin 2.7 on the board.)
If you're using header pins, the upper row of holes in the connector
should overhang the edge of the board. If you're using a floppy drive
motherboard connector, you're golden, of course (unless you have one of
those annoying unkeyed cables, or have accidentally soldered the
connector on in the wrong place --- don't laugh, I've done it.)
2. Plug the drive end of your floppy disk cable into the drive (or drives).
Floppy disk cables typically have [two pairs of floppy disk drive
connectors with a twist between
them](http://www.nullmodem.com/Floppy.htm). (Each pair has one connector
for a 3.5" drive and a different one for a 5.25" drive.) (Some cables
are cheap and just have the 3.5" connectors. Some are _very_ cheap and
have a single 3.5" connector, after the twist.)
FluxEngine uses, sadly, non-standard disk numbering (there are reasons).
Drive 0 is the one nearest the motherboard; that is, before the twist.
Drive 1 is the one at the end of the cable; that is, after the twist.
Drive 0 is the default. If you only have one drive, remember to plug the
drive into the connector _before_ the twist. (Or use `-s :d=1` to select
drive 1 when working with disks.)
3. **Important.** Make sure that no disk you care about is in the drive.
(Because if your wiring is wrong and a disk is inserted, you'll corrupt it.)
4. Connect the floppy drive to power. Nothing should happen. If you've
connected something in backwards, you'll see the drive light up, the
motor start, and if you didn't take the disk out, one track has just
been wiped. If this happens, check your wiring.
5. Connect the FluxEngine to your PC via USB --- using the little socket on
the board, not the big programmer plug.
6. Insert a scratch disk and do `fluxengine rpm` from the shell. The motor
should work and it'll tell you that the disk is spinning at about 300
rpm for a 3.5" disk, or 360 rpm for a 5.25" disk. If it doesn't, please
[get in touch](https://github.com/davidgiven/fluxengine/issues/new).
7. Do `fluxengine test bandwidth` from the shell. It'll measure your USB
bandwidth. Ideally you should be getting above 900kB/s. FluxEngine needs
about 850kB/s, so if you're getting less than this, try a different USB
port.
8. Insert a standard PC formatted floppy disk into the drive (probably a good
idea to remove the old disk first). Then do `fluxengine read ibm`. It
should read the disk, emitting copious diagnostics, and spit out an
`ibm.img` file containing the decoded disk image (either 1440kB or 720kB
depending).
9. Profit!
## Bonus hardware features
For advanced users, the board has a few extra signals which are useful for special purposes.
- Pin 3[0] produces short pulses every 200ms. This is useful for spoofing
index signals to 300 RPM drives; for example, to read flippy disks.
- Pin 3[1] is the same, but produces the pulses every 166ms; this works with
360 RPM drives.
## The programs ## The programs
@@ -81,10 +11,48 @@ moving too quickly for the documentation to keep up. It does respond to
`--help` or `help` depending on context. There are some common properties, `--help` or `help` depending on context. There are some common properties,
described below. described below.
### Core concepts
FluxEngine fundamentally takes file system images and puts them on disk; or
reads the disk and produces a file system image.
A file system image typically has the extension `.img`. It contains a
sector-by-sector record of the _decoded_ data on the disk. For example, on a
disk with 512 byte sectors, one sector will occupy 512 bytes. These are
typically what you want in everyday life.
FluxEngine can also record the raw magnetic data on the disk into a file, which
we call a _flux file_. This contains all the low-level data which the drive
produced as the disk rotated. These are continuous streams of samples from the
disk and are completely useless in day-to-day life. FluxEngine uses its own
format for this, `.flux`, although it's capable of limited interchange with
Kryoflux, Supercard Pro and Catweasel files. A flux file will typically contain
from 80 to 150 kilobytes of data per track.
In general, FluxEngine can use either a real disk or a flux file
interchangeably: you can specify either at (very nearly) any time. A very
common workflow is to read a disk to a flux file, and then reread from the flux
file while changing the decoder options, to save disk wear. It's also much faster.
### Connecting it up
To use, simply plug your FluxEngine into your computer and run the client. If a
single device is plugged in, it will be automatically detected and used.
If _more_ than one device is plugged in, you need to specify which one to use
with the `--device` parameter, which takes the device serial number as a
parameter. You can find out the serial numbers by running the command without
the `--device` parameter, and if more than one device is attached they will be
listed. The serial number is also shown whenever a connection is made.
You _can_ work with more than one FluxEngine at the same time, using different
invocations of the client; but be careful of USB bandwidth. If the devices are
connected via the same hub, the bandwidth will be shared.
### Source and destination specifiers ### Source and destination specifiers
When reading from or writing to _a disk_ (or a file pretending to be a disk), When reading from or writing _flux_ (either from or to a real disk, or a flux
use the `--source` (`-s`) and `--dest` (`-d`) options to tell FluxEngine file), use the `--source` (`-s`) and `--dest` (`-d`) options to tell FluxEngine
which bits of the disk you want to access. These use a common syntax: which bits of the disk you want to access. These use a common syntax:
``` ```
@@ -94,7 +62,7 @@ fluxengine read ibm -s fakedisk.flux:t=0-79:s=0
- To access a real disk, leave out the filename (so `:t=0-79:s=0`). - To access a real disk, leave out the filename (so `:t=0-79:s=0`).
- To access only some tracks, use the `t=` modifier. To access only some - To access only some tracks, use the `t=` modifier. To access only some
sides, use the `s=` modifier. To change drives, use `d=`. sides, use the `s=` modifier.
- Inside a modifier, you can use a comma separated list of ranges. So - Inside a modifier, you can use a comma separated list of ranges. So
`:t=0-3` and `:t=0,1,2,3` are equivalent. `:t=0-3` and `:t=0,1,2,3` are equivalent.
@@ -120,18 +88,15 @@ If you _don't_ specify a modifier, you'll get the default, which should be
sensible for the command you're using. sensible for the command you're using.
**Important note:** FluxEngine _always_ uses zero-based units (even if the **Important note:** FluxEngine _always_ uses zero-based units (even if the
*disk format says otherwise). disk format says otherwise).
### Input and output specifiers ### Input and output specifiers
These use a very similar syntax to the source and destination specifiers When reading or writing _file system images_, use the `--input` (`-i`) and
(because they're based on the same microformat library!) but are used for `--output` (`-o`) options to specify the file and file format. These use a very
input and output _images_: i.e. nicely lined up arrays of sectors which you similar syntax to the source and destination specifiers (because they're based
can actually do something with. on the same microformat library!) but with different specifiers. Also, the
exact format varies according to the extension:
Use `--input` (`-i`) or `--output` (`-o`) as appropriate to tell FluxEngine
where you want to read from or write to. The actual format is autodetected
based on the extension:
- `.img` or `.adf`: raw sector images in CHS order. Append - `.img` or `.adf`: raw sector images in CHS order. Append
`:c=80:h=2:s=9:b=512` to set the geometry; that specifies 80 cylinders, 2 `:c=80:h=2:s=9:b=512` to set the geometry; that specifies 80 cylinders, 2
@@ -167,6 +132,10 @@ based on the extension:
FluxEngine's D64 support is currently limited to write only. It will work FluxEngine's D64 support is currently limited to write only. It will work
with up to 40 logical tracks. with up to 40 logical tracks.
- `.diskcopy`: a Macintosh DiskCopy 4.2 file. This is a special-purpose
format due to the weird layout of Mac GCR disks, but it can also support
720kB and 1440kB IBM disks (although there's no real benefit).
### High density disks ### High density disks
High density disks use a different magnetic medium to low and double density High density disks use a different magnetic medium to low and double density
@@ -191,17 +160,23 @@ here.](http://www.retrotechnology.com/herbs_stuff/guzis.html)
These flags apply to many operations and are useful for modifying the overall These flags apply to many operations and are useful for modifying the overall
behaviour. behaviour.
- `--revolutions=X`: when reading, spin the disk X times. Many formats - `--revolutions=X`: when reading, spin the disk X times. X can be a floating
require `--revolutions=2` (which should happen automatically); or you can point number. The default is usually 1.25. Some formats default to 1.
increase the number to sample more data. Increasing the number will sample more data, and can be useful on dubious
disks to try and get a better read.
- `--sync-with-index=true|false`: wait for an index pulse before starting to
read the disk. (Ignored for write operations.) By default FluxEngine
doesn't, as it makes reads faster, but when diagnosing disk problems it's
helpful to have all your data start at the same place each time.
- `--index-source=X`, `--write-index-source=X`: set the source of index - `--index-source=X`, `--write-index-source=X`: set the source of index
pulses when reading or writing respectively. This is for use with drives pulses when reading or writing respectively. This is for use with drives
which don't produce index pulse data. Use 0 to get index pulses from the which don't produce index pulse data. Use 0 to get index pulses from the
drive, 1 to fake 300RPM pulses, or 2 to fake 360RPM pulses. Note this has drive, 1 to fake 300RPM pulses, or 2 to fake 360RPM pulses. Note this has
no effect on the _drive_, so it doesn't help with flippy disks, but is no effect on the _drive_, so it doesn't help with flippy disks, but is
useful for using very old drives with FluxEngine itself. If you use this useful for using very old drives with FluxEngine itself. If you use this
option, then any index marks in the sampled flux are, of course, garbage. option, then any index marks in the sampled flux are, of course, garbage.
### The commands ### The commands
@@ -211,60 +186,62 @@ installed anywhere and after building you'll find them in the `.obj`
directory. directory.
- `fluxengine erase`: wipes (all or part of) a disk --- erases it without - `fluxengine erase`: wipes (all or part of) a disk --- erases it without
writing a pulsetrain. writing a pulsetrain.
- `fluxengine inspect`: dumps the raw pulsetrain / bitstream to stdout. - `fluxengine inspect`: dumps the raw pulsetrain / bitstream to stdout.
Mainly useful for debugging. Mainly useful for debugging.
- `fluxengine read *`: reads various formats of disk. See the per-format - `fluxengine read *`: reads various formats of disk. See the per-format
documentation linked from the table above. These all take an optional documentation linked from the table [in the index page](../README.md).
`--write-flux` option which will cause the raw flux to be written to the These all take an optional `--write-flux` option which will cause the raw
specified file. There are various `--dump` options for showing raw data flux to be written to the specified file as well as the normal decode.
during the decode process. There are various `--dump` options for showing raw data during the decode
process, and `--write-csv` will write a copious CSV report of the state of
every sector in the file in machine-readable format.
- `fluxengine write *`: writes various formats of disk. Again, see the - `fluxengine write *`: writes various formats of disk. Again, see the
per-format documentation above. per-format documentation [in the index page](../README.md).
- `fluxengine writeflux`: writes raw flux files. This is much less useful - `fluxengine writeflux`: writes raw flux files. This is much less useful
than you might think: you can't write flux files read from a disk to than you might think: you can't reliably write flux files read from a disk
another disk. (See the [FAQ](faq.md) for more information.) It's mainly to another disk. (See the [FAQ](faq.md) for more information.) It's mainly
useful for flux files synthesised by the other `fluxengine write` commands. useful for flux files synthesised by the other `fluxengine write` commands.
- `fluxengine writetestpattern`: writes regular pulses (at a configurable - `fluxengine writetestpattern`: writes regular pulses (at a configurable
interval) to the disk. Useful for testing drive jitter, erasing disks in a interval) to the disk. Useful for testing drive jitter, erasing disks in a
more secure fashion, or simply debugging. Goes well with `fluxengine more secure fashion, or simply debugging. Goes well with `fluxengine
inspect`. inspect`.
- `fluxengine rpm`: measures the RPM of the drive (requires a disk in the - `fluxengine rpm`: measures the RPM of the drive (requires a disk in the
drive). Mainly useful for testing. drive). Mainly useful for testing.
- `fluxengine seek`: moves the head. Mainly useful for finding out whether - `fluxengine seek`: moves the head. Mainly useful for finding out whether
your drive can seek to track 82. (Mine can't.) your drive can seek to track 82. (Mine can't.)
- `fluxengine test bandwidth`: measures your USB throughput. You need about - `fluxengine test bandwidth`: measures your USB throughput. You don't need
600kB/s for FluxEngine to work for DD disks, and 900 to 950kB/s for HD a disk in the drive for this one.
disks. You don't need a disk in the drive for this one.
- `fluxengine test voltages`: measures your FDD bus signal voltages, which - `fluxengine test voltages`: measures your FDD bus signal voltages, which is
is useful for testing for termination issues. useful for testing for termination issues.
- `fluxengine upgradefluxfile`: occasionally I need to upgrade the flux - `fluxengine upgradefluxfile`: occasionally I need to upgrade the flux file
file format in a non-backwards-compatible way; this tool will upgrade flux format in a non-backwards-compatible way; this tool will upgrade flux files
files to the new format. to the new format.
- `fluxengine convert`: converts flux files from various formats to various - `fluxengine convert`: converts flux files from various formats to various
other formats. You can use this to convert Catweasel flux files to other formats. You can use this to convert Catweasel flux files to
FluxEngine's native format, FluxEngine flux files to various other formats FluxEngine's native format, FluxEngine flux files to various other formats
useful for debugging (including VCD which can be loaded into useful for debugging (including VCD which can be loaded into
[sigrok](http://sigrok.org)), and bidirectional conversion to and from [sigrok](http://sigrok.org)), and bidirectional conversion to and from
Supercard Pro `.scp` format. Supercard Pro `.scp` format.
**Important SCP note:** import (`fluxengine convert scptoflux`) should be **Important SCP note:** import (`fluxengine convert scptoflux`) should be
fairly robust, but export (`fluxengine convert fluxtoscp`) should only be fairly robust, but export (`fluxengine convert fluxtoscp`) should only be
done with great caution as FluxEngine files contain features which can't be done with great caution as FluxEngine files contain features which can't be
represented very well in `.scp` format and they're probably pretty dubious. represented very well in `.scp` format and they're probably pretty dubious.
As ever, please [get in As ever, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new) with any reports. touch](https://github.com/davidgiven/fluxengine/issues/new) with any
reports.
Commands which normally take `--source` or `--dest` get a sensible default if Commands which normally take `--source` or `--dest` get a sensible default if
left unspecified. `fluxengine read ibm` on its own will read drive 0 and left unspecified. `fluxengine read ibm` on its own will read drive 0 and
@@ -290,8 +267,10 @@ disk). For a 5.25" disk, use `--visualiser-period=166`.
Supplied with FluxEngine, but not part of FluxEngine, are some little tools I Supplied with FluxEngine, but not part of FluxEngine, are some little tools I
wrote to do useful things. These are built alongside FluxEngine. wrote to do useful things. These are built alongside FluxEngine.
- `brother120tool`: extracts files from a 120kB Brother filesystem image. - `brother120tool`, `brother240tool`: does things to Brother word processor
disks. These are [documented on the Brother disk format
page](disk-brother.md).
## The recommended workflow ## The recommended workflow
So you've just received, say, a huge pile of old Brother word processor disks So you've just received, say, a huge pile of old Brother word processor disks

View File

@@ -1,7 +1,6 @@
#include "globals.h" #include "globals.h"
#include "bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "common/crunch.h"
#include <fstream> #include <fstream>
#include <zlib.h> #include <zlib.h>
@@ -249,60 +248,6 @@ Bytes Bytes::decompress() const
return output; return output;
} }
Bytes Bytes::crunch() const
{
Bytes output;
ByteWriter bw(output);
Bytes outputBuffer(1024*1024);
crunch_state_t cs = {};
cs.inputptr = begin();
cs.inputlen = size();
do
{
cs.outputptr = outputBuffer.begin();
cs.outputlen = outputBuffer.size();
::crunch(&cs);
bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen);
}
while (cs.inputlen != 0);
cs.outputptr = outputBuffer.begin();
cs.outputlen = outputBuffer.size();
donecrunch(&cs);
bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen);
return output;
}
Bytes Bytes::uncrunch() const
{
Bytes output;
ByteWriter bw(output);
Bytes outputBuffer(1024*1024);
crunch_state_t cs = {};
cs.inputptr = begin();
cs.inputlen = size();
do
{
cs.outputptr = outputBuffer.begin();
cs.outputlen = outputBuffer.size();
::uncrunch(&cs);
bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen);
}
while (cs.inputlen != 0);
cs.outputptr = outputBuffer.begin();
cs.outputlen = outputBuffer.size();
doneuncrunch(&cs);
bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen);
return output;
}
void Bytes::writeToFile(const std::string& filename) const void Bytes::writeToFile(const std::string& filename) const
{ {
std::ofstream f(filename, std::ios::out | std::ios::binary); std::ofstream f(filename, std::ios::out | std::ios::binary);
@@ -313,6 +258,11 @@ void Bytes::writeToFile(const std::string& filename) const
f.close(); f.close();
} }
void Bytes::writeTo(std::ostream& stream) const
{
stream.write((const char*) cbegin(), size());
}
ByteReader Bytes::reader() const ByteReader Bytes::reader() const
{ {
return ByteReader(*this); return ByteReader(*this);

View File

@@ -1,6 +1,8 @@
#ifndef BYTES_H #ifndef BYTES_H
#define BYTES_H #define BYTES_H
#include <string.h>
class ByteReader; class ByteReader;
class ByteWriter; class ByteWriter;
@@ -10,6 +12,7 @@ public:
Bytes(); Bytes();
Bytes(unsigned size); Bytes(unsigned size);
Bytes(const uint8_t* ptr, size_t len); Bytes(const uint8_t* ptr, size_t len);
Bytes(const std::string& data);
Bytes(std::initializer_list<uint8_t> data); Bytes(std::initializer_list<uint8_t> data);
Bytes(std::shared_ptr<std::vector<uint8_t>> data); Bytes(std::shared_ptr<std::vector<uint8_t>> data);
Bytes(std::shared_ptr<std::vector<uint8_t>> data, unsigned start, unsigned end); Bytes(std::shared_ptr<std::vector<uint8_t>> data, unsigned start, unsigned end);
@@ -51,14 +54,13 @@ public:
Bytes swab() const; Bytes swab() const;
Bytes compress() const; Bytes compress() const;
Bytes decompress() const; Bytes decompress() const;
Bytes crunch() const;
Bytes uncrunch() const;
std::vector<bool> toBits() const; std::vector<bool> toBits() const;
ByteReader reader() const; ByteReader reader() const;
ByteWriter writer(); ByteWriter writer();
void writeToFile(const std::string& filename) const; void writeToFile(const std::string& filename) const;
void writeTo(std::ostream& stream) const;
private: private:
std::shared_ptr<std::vector<uint8_t>> _data; std::shared_ptr<std::vector<uint8_t>> _data;
@@ -272,6 +274,16 @@ public:
ByteWriter& operator += (std::istream& stream); ByteWriter& operator += (std::istream& stream);
ByteWriter& append(const char* data)
{
return *this += Bytes((const uint8_t*)data, strlen(data));
}
ByteWriter& append(const std::string& data)
{
return *this += Bytes(data);
}
ByteWriter& append(const Bytes data) ByteWriter& append(const Bytes data)
{ {
return *this += data; return *this += data;

View File

@@ -1,125 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
#include "crunch.h"
void crunch(crunch_state_t* state)
{
while (state->inputlen && state->outputlen)
{
uint8_t data = *state->inputptr++;
state->inputlen--;
if (data == 0x80)
{
/* Multiple 0x80s in a row get swallowed as they're
* meaningless. */
state->haspending = true;
}
else if (data & 0x80)
{
if (state->haspending)
{
state->fifo = (state->fifo << 3) | 4;
state->fifolen += 3;
state->haspending = false;
}
state->fifo = (state->fifo << 3) | 4 | (data & 1);
state->fifolen += 3;
}
else
{
if (state->haspending && (data >= 0x40))
{
state->fifo = (state->fifo << 3) | 4;
state->fifolen += 3;
state->haspending = false;
}
state->fifo = (state->fifo << 8) | data;
if (state->haspending)
state->fifo |= 0xc0;
state->haspending = false;
state->fifolen += 8;
}
if (state->fifolen >= 8)
{
data = state->fifo >> (state->fifolen - 8);
*state->outputptr++ = data;
state->outputlen--;
state->fifolen -= 8;
}
}
}
void donecrunch(crunch_state_t* state)
{
if (state->fifolen > 0)
{
uint8_t b = 0;
state->inputptr = &b;
state->inputlen = 1;
crunch(state);
}
}
void uncrunch(crunch_state_t* state)
{
while (state->inputlen && state->outputlen)
{
if (state->haspending)
{
*state->outputptr++ = state->pendingbyte;
state->outputlen--;
state->haspending = false;
continue;
}
if (state->fifolen < 8)
{
if (state->inputlen)
{
state->fifo = (state->fifo << 8) | *state->inputptr++;
state->inputlen--;
state->fifolen += 8;
}
else
state->fifo <<= 8;
}
uint8_t data = state->fifo >> (state->fifolen - 8);
switch (data & 0xc0)
{
case 0x80:
data = ((data >> 5) & 0x01) | 0x80;
state->fifolen -= 3;
break;
case 0xc0:
state->haspending = true;
state->pendingbyte = data & 0x3f;
data = 0x80;
/* fall through */
default:
state->fifolen -= 8;
}
if (data)
{
*state->outputptr++ = data;
state->outputlen--;
}
}
}
void doneuncrunch(crunch_state_t* state)
{
if (state->fifolen > 0)
{
uint8_t b = 0;
state->inputptr = &b;
state->inputlen = 1;
uncrunch(state);
}
}

View File

@@ -1,48 +0,0 @@
#ifndef CRUNCH_H
#define CRUNCH_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* To save bandwidth, we compress the byte stream from the sampler when
* sending it over USB. The encoding used is:
*
* 0nnn.nnnn: value 0x00..0x7f
* 11nn.nnnn: value 0x80 then 0x00..0x3f
* 10n : value 0x80|n
*
* The end of the buffer is terminated with zeroes, which are ignored
* (not written to the output).
*
* This saves ~40%, which gets us in under the bandwidth cap.
*/
typedef struct crunch_state_t
{
const uint8_t* inputptr;
uint32_t inputlen;
uint8_t* outputptr;
uint32_t outputlen;
uint32_t fifo;
uint8_t fifolen;
bool haspending;
uint8_t pendingbyte;
}
crunch_state_t;
/* Crunches as much as possible and then stops. */
extern void crunch(crunch_state_t* state);
extern void donecrunch(crunch_state_t* state);
/* Uncrunches as much as possible and then stops. */
extern void uncrunch(crunch_state_t* state);
extern void doneuncrunch(crunch_state_t* state);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -15,34 +15,31 @@ std::vector<std::string> DataSpec::split(
{ {
std::vector<std::string> ret; std::vector<std::string> ret;
size_t start = 0; if (!s.empty())
size_t end = 0; {
size_t len = 0; size_t start = 0;
do size_t end = 0;
{ size_t len = 0;
end = s.find(delimiter,start); do
len = end - start; {
std::string token = s.substr(start, len); end = s.find(delimiter,start);
ret.emplace_back( token ); len = end - start;
start += len + delimiter.length(); std::string token = s.substr(start, len);
} ret.emplace_back( token );
while (end != std::string::npos); start += len + delimiter.length();
}
while (end != std::string::npos);
}
return ret; return ret;
} }
DataSpec::Modifier DataSpec::parseMod(const std::string& spec) std::set<unsigned> DataSpec::parseRange(const std::string& data)
{ {
static const std::regex MOD_REGEX("([a-z]*)=([-x+0-9,]*)"); static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
std::smatch match; std::set<unsigned> result;
if (!std::regex_match(spec, match, MOD_REGEX)) for (auto& data : split(data, ","))
Error() << "invalid data modifier syntax '" << spec << "'";
Modifier m;
m.name = match[1];
m.source = spec;
for (auto& data : split(match[2], ","))
{ {
int start = 0; int start = 0;
int count = 1; int count = 1;
@@ -64,9 +61,24 @@ DataSpec::Modifier DataSpec::parseMod(const std::string& spec)
Error() << "mod '" << data << "' specifies an illegal quantity"; Error() << "mod '" << data << "' specifies an illegal quantity";
for (int i = start; i < (start+count); i += step) for (int i = start; i < (start+count); i += step)
m.data.insert(i); result.insert(i);
} }
return result;
}
DataSpec::Modifier DataSpec::parseMod(const std::string& spec)
{
static const std::regex MOD_REGEX("([a-z]*)=([-x+0-9,]*)");
std::smatch match;
if (!std::regex_match(spec, match, MOD_REGEX))
Error() << "invalid data modifier syntax '" << spec << "'";
Modifier m;
m.name = match[1];
m.source = spec;
m.data = parseRange(match[2]);
return m; return m;
} }
@@ -74,7 +86,7 @@ void DataSpec::set(const std::string& spec)
{ {
std::vector<std::string> words = split(spec, ":"); std::vector<std::string> words = split(spec, ":");
if (words.size() == 0) if (words.size() == 0)
Error() << "empty data specification (you have to specify *something*)"; return;
filename = words[0]; filename = words[0];
if (words.size() > 1) if (words.size() > 1)

View File

@@ -34,6 +34,8 @@ public:
public: public:
static std::vector<std::string> split( static std::vector<std::string> split(
const std::string& s, const std::string& delimiter); const std::string& s, const std::string& delimiter);
static std::set<unsigned> parseRange(const std::string& spec);
static Modifier parseMod(const std::string& spec); static Modifier parseMod(const std::string& spec);
public: public:
@@ -117,4 +119,34 @@ private:
DataSpec _value; DataSpec _value;
}; };
class RangeFlag : public Flag
{
public:
RangeFlag(const std::vector<std::string>& names, const std::string helptext,
const std::string& defaultValue):
Flag(names, helptext),
_stringValue(defaultValue),
_value(DataSpec::parseRange(defaultValue))
{}
const std::set<unsigned>& get() const
{ checkInitialised(); return _value; }
operator const std::set<unsigned>& () const
{ return get(); }
bool hasArgument() const { return true; }
const std::string defaultValueAsString() const { return _stringValue; }
void set(const std::string& value)
{
_stringValue = value;
_value = DataSpec::parseRange(value);
}
private:
std::string _stringValue;
std::set<unsigned> _value;
};
#endif #endif

View File

@@ -35,7 +35,7 @@ void AbstractDecoder::decodeToSectors(Track& track)
return; return;
if ((r == UNKNOWN_RECORD) || (r == DATA_RECORD)) if ((r == UNKNOWN_RECORD) || (r == DATA_RECORD))
{ {
fmr.readNextMatchingOpcode(F_OP_PULSE); fmr.findEvent(F_BIT_PULSE);
continue; continue;
} }
@@ -56,7 +56,7 @@ void AbstractDecoder::decodeToSectors(Track& track)
r = advanceToNextRecord(); r = advanceToNextRecord();
if (r != UNKNOWN_RECORD) if (r != UNKNOWN_RECORD)
break; break;
if (fmr.readNextMatchingOpcode(F_OP_PULSE) == 0) if (fmr.findEvent(F_BIT_PULSE) == 0)
break; break;
} }
recordStart = fmr.tell(); recordStart = fmr.tell();
@@ -88,3 +88,10 @@ void AbstractDecoder::pushRecord(const Fluxmap::Position& start, const Fluxmap::
_track->rawrecords.push_back(record); _track->rawrecords.push_back(record);
_fmr->seek(here); _fmr->seek(here);
} }
std::set<unsigned> AbstractDecoder::requiredSectors(Track& track) const
{
static std::set<unsigned> empty;
return empty;
}

View File

@@ -52,6 +52,11 @@ public:
void seek(const Fluxmap::Position& pos) void seek(const Fluxmap::Position& pos)
{ return _fmr->seek(pos); } { return _fmr->seek(pos); }
/* Returns a set of sectors required to exist on this track. If the reader
* sees any missing, it will consider this to be an error and will retry
* the read. */
virtual std::set<unsigned> requiredSectors(Track& track) const;
protected: protected:
virtual void beginTrack() {}; virtual void beginTrack() {};
virtual RecordType advanceToNextRecord() = 0; virtual RecordType advanceToNextRecord() = 0;

View File

@@ -30,16 +30,15 @@ static DoubleFlag minimumClockUs(
"Refuse to detect clocks shorter than this, to avoid false positives.", "Refuse to detect clocks shorter than this, to avoid false positives.",
0.75); 0.75);
int FluxmapReader::readOpcode(unsigned& ticks) uint8_t FluxmapReader::getNextEvent(unsigned& ticks)
{ {
ticks = 0; ticks = 0;
while (!eof()) while (!eof())
{ {
uint8_t b = _bytes[_pos.bytes++]; uint8_t b = _bytes[_pos.bytes++];
if (b < 0x80) ticks += b & 0x3f;
ticks += b; if (b & (F_BIT_PULSE|F_BIT_INDEX))
else
{ {
_pos.ticks += ticks; _pos.ticks += ticks;
return b; return b;
@@ -47,21 +46,21 @@ int FluxmapReader::readOpcode(unsigned& ticks)
} }
_pos.ticks += ticks; _pos.ticks += ticks;
return -1; return 0;
} }
unsigned FluxmapReader::readNextMatchingOpcode(uint8_t opcode) unsigned FluxmapReader::findEvent(uint8_t target)
{ {
unsigned ticks = 0; unsigned ticks = 0;
for (;;) for (;;)
{ {
unsigned thisTicks; unsigned thisTicks;
int op = readOpcode(thisTicks); uint8_t bits = getNextEvent(thisTicks);
ticks += thisTicks; ticks += thisTicks;
if (op == -1) if (eof())
return 0; return 0;
if (op == opcode) if (bits & target)
return ticks; return ticks;
} }
} }
@@ -73,7 +72,7 @@ unsigned FluxmapReader::readInterval(nanoseconds_t clock)
while (ticks < thresholdTicks) while (ticks < thresholdTicks)
{ {
unsigned thisTicks = readNextMatchingOpcode(F_OP_PULSE); unsigned thisTicks = findEvent(F_BIT_PULSE);
if (!thisTicks) if (!thisTicks)
break; break;
ticks += thisTicks; ticks += thisTicks;
@@ -196,7 +195,7 @@ void FluxmapReader::seek(nanoseconds_t ns)
while (!eof() && (_pos.ticks < ticks)) while (!eof() && (_pos.ticks < ticks))
{ {
unsigned t; unsigned t;
readOpcode(t); getNextEvent(t);
} }
_pos.zeroes = 0; _pos.zeroes = 0;
} }
@@ -237,7 +236,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
positions[i] = positions[i+1]; positions[i] = positions[i+1];
candidates[i] = candidates[i+1]; candidates[i] = candidates[i+1];
} }
candidates[intervalCount] = readNextMatchingOpcode(F_OP_PULSE); candidates[intervalCount] = findEvent(F_BIT_PULSE);
positions[intervalCount] = tell(); positions[intervalCount] = tell();
} }
@@ -248,7 +247,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
void FluxmapReader::seekToIndexMark() void FluxmapReader::seekToIndexMark()
{ {
readNextMatchingOpcode(F_OP_INDEX); findEvent(F_BIT_INDEX);
_pos.zeroes = 0; _pos.zeroes = 0;
} }

View File

@@ -96,8 +96,8 @@ public:
_pos = pos; _pos = pos;
} }
int readOpcode(unsigned& ticks); uint8_t getNextEvent(unsigned& ticks);
unsigned readNextMatchingOpcode(uint8_t opcode); unsigned findEvent(uint8_t bits);
unsigned readInterval(nanoseconds_t clock); /* with debounce support */ unsigned readInterval(nanoseconds_t clock); /* with debounce support */
/* Important! You can only reliably seek to 1 bits. */ /* Important! You can only reliably seek to 1 bits. */

View File

@@ -4,6 +4,9 @@
Fluxmap& Fluxmap::appendBytes(const Bytes& bytes) Fluxmap& Fluxmap::appendBytes(const Bytes& bytes)
{ {
if (bytes.size() == 0)
return *this;
return appendBytes(&bytes[0], bytes.size()); return appendBytes(&bytes[0], bytes.size());
} }
@@ -15,8 +18,7 @@ Fluxmap& Fluxmap::appendBytes(const uint8_t* ptr, size_t len)
while (len--) while (len--)
{ {
uint8_t byte = *ptr++; uint8_t byte = *ptr++;
if (byte < 0x80) _ticks += byte & 0x3f;
_ticks += byte;
bw.write_8(byte); bw.write_8(byte);
} }
@@ -24,12 +26,19 @@ Fluxmap& Fluxmap::appendBytes(const uint8_t* ptr, size_t len)
return *this; return *this;
} }
uint8_t& Fluxmap::findLastByte()
{
if (_bytes.empty())
appendByte(0x00);
return *(_bytes.end() - 1);
}
Fluxmap& Fluxmap::appendInterval(uint32_t ticks) Fluxmap& Fluxmap::appendInterval(uint32_t ticks)
{ {
while (ticks >= 0x7f) while (ticks >= 0x3f)
{ {
appendByte(0x7f); appendByte(0x3f);
ticks -= 0x7f; ticks -= 0x3f;
} }
appendByte((uint8_t)ticks); appendByte((uint8_t)ticks);
return *this; return *this;
@@ -37,13 +46,13 @@ Fluxmap& Fluxmap::appendInterval(uint32_t ticks)
Fluxmap& Fluxmap::appendPulse() Fluxmap& Fluxmap::appendPulse()
{ {
appendByte(0x80); findLastByte() |= 0x80;
return *this; return *this;
} }
Fluxmap& Fluxmap::appendIndex() Fluxmap& Fluxmap::appendIndex()
{ {
appendByte(0x81); findLastByte() |= 0x40;
return *this; return *this;
} }
@@ -54,25 +63,26 @@ void Fluxmap::precompensate(int threshold_ticks, int amount_ticks)
for (unsigned i=0; i<_bytes.size(); i++) for (unsigned i=0; i<_bytes.size(); i++)
{ {
uint8_t& prev = (i == 0) ? junk : _bytes[i-1]; uint8_t& prev = (i == 0) ? junk : _bytes[i-1];
uint8_t curr = _bytes[i]; uint8_t prevticks = prev & 0x3f;
uint8_t currticks = _bytes[i] & 0x3f;
if (curr < (3*threshold_ticks)) if (currticks < (3*threshold_ticks))
{ {
if ((prev <= threshold_ticks) && (curr > threshold_ticks)) if ((prevticks <= threshold_ticks) && (currticks > threshold_ticks))
{ {
/* 01001; move the previous bit backwards. */ /* 01001; move the previous bit backwards. */
if (prev >= (1+amount_ticks)) if (prevticks >= (1+amount_ticks))
prev -= amount_ticks; prev -= amount_ticks;
if (curr <= (0x7f-amount_ticks)) if (currticks <= (0x7f-amount_ticks))
curr += amount_ticks; currticks += amount_ticks;
} }
else if ((prev > threshold_ticks) && (curr <= threshold_ticks)) else if ((prevticks > threshold_ticks) && (currticks <= threshold_ticks))
{ {
/* 00101; move the current bit forwards. */ /* 00101; move the current bit forwards. */
if (prev <= (0x7f-amount_ticks)) if (prevticks <= (0x7f-amount_ticks))
prev += amount_ticks; prev += amount_ticks;
if (curr >= (1+amount_ticks)) if (currticks >= (1+amount_ticks))
curr -= amount_ticks; currticks -= amount_ticks;
} }
} }
} }

View File

@@ -48,6 +48,9 @@ public:
void precompensate(int threshold_ticks, int amount_ticks); void precompensate(int threshold_ticks, int amount_ticks);
private:
uint8_t& findLastByte();
private: private:
nanoseconds_t _duration = 0; nanoseconds_t _duration = 0;
int _ticks = 0; int _ticks = 0;

View File

@@ -1,10 +1,12 @@
#include "globals.h" #include "globals.h"
#include "flags.h" #include "flags.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "usb.h" #include "usb/usb.h"
#include "fluxsink/fluxsink.h" #include "fluxsink/fluxsink.h"
FlagGroup hardwareFluxSinkFlags; FlagGroup hardwareFluxSinkFlags = {
&usbFlags,
};
static bool high_density = false; static bool high_density = false;
@@ -36,8 +38,7 @@ public:
usbSetDrive(_drive, high_density, indexMode); usbSetDrive(_drive, high_density, indexMode);
usbSeek(track); usbSeek(track);
Bytes crunched = fluxmap.rawBytes().crunch(); return usbWrite(side, fluxmap.rawBytes());
return usbWrite(side, crunched);
} }
private: private:

View File

@@ -1,11 +1,13 @@
#include "globals.h" #include "globals.h"
#include "flags.h" #include "flags.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "usb.h" #include "usb/usb.h"
#include "fluxsource/fluxsource.h" #include "fluxsource/fluxsource.h"
#include "fmt/format.h" #include "fmt/format.h"
FlagGroup hardwareFluxSourceFlags; FlagGroup hardwareFluxSourceFlags = {
&usbFlags
};
static DoubleFlag revolutions( static DoubleFlag revolutions(
{ "--revolutions" }, { "--revolutions" },
@@ -50,9 +52,9 @@ public:
{ {
usbSetDrive(_drive, high_density, indexMode); usbSetDrive(_drive, high_density, indexMode);
usbSeek(track); usbSeek(track);
Bytes crunched = usbRead(side, synced, revolutions * _oneRevolution); Bytes data = usbRead(side, synced, revolutions * _oneRevolution);
auto fluxmap = std::make_unique<Fluxmap>(); auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBytes(crunched.uncrunch()); fluxmap->appendBytes(data);
return fluxmap; return fluxmap;
} }

View File

@@ -12,7 +12,7 @@
#include <set> #include <set>
#include <cassert> #include <cassert>
typedef int nanoseconds_t; typedef double nanoseconds_t;
class Bytes; class Bytes;
extern double getCurrentTime(); extern double getCurrentTime();

View File

@@ -1,25 +0,0 @@
#include "globals.h"
#include "image.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"
#include "imagewriter/imagewriter.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
#include <fstream>
SectorSet readSectorsFromFile(const ImageSpec& spec)
{
return ImageReader::create(spec)->readImage();
}
void writeSectorsToFile(const SectorSet& sectors, const ImageSpec& spec)
{
std::unique_ptr<ImageWriter> writer(ImageWriter::create(sectors, spec));
writer->adjustGeometry();
writer->printMap();
writer->writeImage();
}

View File

@@ -1,14 +0,0 @@
#ifndef IMAGE_H
#define IMAGE_H
class SectorSet;
class ImageSpec;
extern SectorSet readSectorsFromFile(
const ImageSpec& filename);
extern void writeSectorsToFile(
const SectorSet& sectors,
const ImageSpec& filename);
#endif

View File

@@ -1,17 +1,19 @@
#include "globals.h" #include "globals.h"
#include "image.h"
#include "flags.h" #include "flags.h"
#include "dataspec.h" #include "dataspec.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "imagereader/imagereader.h" #include "imagereader/imagereader.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <algorithm>
#include <ctype.h>
std::map<std::string, ImageReader::Constructor> ImageReader::formats = std::map<std::string, ImageReader::Constructor> ImageReader::formats =
{ {
{".adf", ImageReader::createImgImageReader}, {".adf", ImageReader::createImgImageReader},
{".d81", ImageReader::createImgImageReader}, {".d81", ImageReader::createImgImageReader},
{".img", ImageReader::createImgImageReader}, {".img", ImageReader::createImgImageReader},
{".ima", ImageReader::createImgImageReader},
}; };
static bool ends_with(const std::string& value, const std::string& ending) static bool ends_with(const std::string& value, const std::string& ending)

View File

@@ -1,5 +1,4 @@
#include "globals.h" #include "globals.h"
#include "image.h"
#include "flags.h" #include "flags.h"
#include "dataspec.h" #include "dataspec.h"
#include "sector.h" #include "sector.h"

View File

@@ -1,5 +1,4 @@
#include "globals.h" #include "globals.h"
#include "image.h"
#include "flags.h" #include "flags.h"
#include "dataspec.h" #include "dataspec.h"
#include "sector.h" #include "sector.h"

View File

@@ -0,0 +1,169 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagewriter/imagewriter.h"
#include "fmt/format.h"
#include "ldbs.h"
#include <algorithm>
#include <iostream>
#include <fstream>
static const char LABEL[] = "FluxEngine image";
static void write_and_update_checksum(ByteWriter& bw, uint32_t& checksum, const Bytes& data)
{
ByteReader br(data);
while (!br.eof())
{
uint32_t i = br.read_be16();
checksum += i;
checksum = (checksum >> 1) | (checksum << 31);
bw.write_be16(i);
}
}
class DiskCopyImageWriter : public ImageWriter
{
public:
DiskCopyImageWriter(const SectorSet& sectors, const ImageSpec& spec):
ImageWriter(sectors, spec)
{}
void writeImage()
{
bool mfm = false;
if (spec.bytes == 524)
{
/* GCR disk */
}
else if (spec.bytes == 512)
{
/* MFM disk */
mfm = true;
}
else
Error() << "this image is not compatible with the DiskCopy 4.2 format";
std::cout << "writing DiskCopy 4.2 image\n"
<< fmt::format("{} tracks, {} heads, {} sectors, {} bytes per sector; {}\n",
spec.cylinders, spec.heads, spec.sectors, spec.bytes,
mfm ? "MFM" : "GCR");
auto sectors_per_track = [&](int track) -> int
{
if (mfm)
return spec.sectors;
if (track < 16)
return 12;
if (track < 32)
return 11;
if (track < 48)
return 10;
if (track < 64)
return 9;
return 8;
};
Bytes image;
ByteWriter bw(image);
/* Write the actual sectr data. */
uint32_t dataChecksum = 0;
uint32_t tagChecksum = 0;
uint32_t offset = 0x54;
uint32_t sectorDataStart = offset;
for (int track = 0; track < spec.cylinders; track++)
{
for (int head = 0; head < spec.heads; head++)
{
int sectorCount = sectors_per_track(track);
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
{
const auto& sector = sectors.get(track, head, sectorId);
if (sector)
{
bw.seek(offset);
write_and_update_checksum(bw, dataChecksum, sector->data.slice(0, 512));
}
offset += 512;
}
}
}
uint32_t sectorDataEnd = offset;
if (!mfm)
{
for (int track = 0; track < spec.cylinders; track++)
{
for (int head = 0; head < spec.heads; head++)
{
int sectorCount = sectors_per_track(track);
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
{
const auto& sector = sectors.get(track, head, sectorId);
if (sector)
{
bw.seek(offset);
write_and_update_checksum(bw, tagChecksum, sector->data.slice(512, 12));
}
offset += 12;
}
}
}
}
uint32_t tagDataEnd = offset;
/* Write the header. */
uint8_t encoding;
uint8_t format;
if (mfm)
{
format = 0x22;
if (spec.sectors == 18)
encoding = 3;
else
encoding = 2;
}
else
{
if (spec.heads == 2)
{
encoding = 1;
format = 0x22;
}
else
{
encoding = 0;
format = 0x02;
}
}
bw.seek(0);
bw.write_8(sizeof(LABEL));
bw.append(LABEL);
bw.seek(0x40);
bw.write_be32(sectorDataEnd - sectorDataStart); /* data size */
bw.write_be32(tagDataEnd - sectorDataEnd); /* tag size */
bw.write_be32(dataChecksum); /* data checksum */
bw.write_be32(tagChecksum); /* tag checksum */
bw.write_8(encoding); /* encoding */
bw.write_8(format); /* format byte */
bw.write_be16(0x0100); /* magic number */
image.writeToFile(spec.filename);
}
};
std::unique_ptr<ImageWriter> ImageWriter::createDiskCopyImageWriter(
const SectorSet& sectors, const ImageSpec& spec)
{
return std::unique_ptr<ImageWriter>(new DiskCopyImageWriter(sectors, spec));
}

View File

@@ -1,17 +1,19 @@
#include "globals.h" #include "globals.h"
#include "image.h"
#include "flags.h" #include "flags.h"
#include "dataspec.h" #include "dataspec.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "imagewriter/imagewriter.h" #include "imagewriter/imagewriter.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <iostream>
#include <fstream>
std::map<std::string, ImageWriter::Constructor> ImageWriter::formats = std::map<std::string, ImageWriter::Constructor> ImageWriter::formats =
{ {
{".adf", ImageWriter::createImgImageWriter}, {".adf", ImageWriter::createImgImageWriter},
{".d64", ImageWriter::createD64ImageWriter}, {".d64", ImageWriter::createD64ImageWriter},
{".d81", ImageWriter::createImgImageWriter}, {".d81", ImageWriter::createImgImageWriter},
{".diskcopy", ImageWriter::createDiskCopyImageWriter},
{".img", ImageWriter::createImgImageWriter}, {".img", ImageWriter::createImgImageWriter},
{".ldbs", ImageWriter::createLDBSImageWriter}, {".ldbs", ImageWriter::createLDBSImageWriter},
}; };
@@ -63,6 +65,56 @@ void ImageWriter::adjustGeometry()
} }
} }
void ImageWriter::writeCsv(const std::string& filename)
{
std::ofstream f(filename, std::ios::out);
if (!f.is_open())
Error() << "cannot open CSV report file";
f << "\"Physical track\","
"\"Physical side\","
"\"Logical track\","
"\"Logical side\","
"\"Logical sector\","
"\"Clock (ns)\","
"\"Header start (ns)\","
"\"Header end (ns)\","
"\"Data start (ns)\","
"\"Data end (ns)\","
"\"Raw data address (bytes)\","
"\"User payload length (bytes)\","
"\"Status\""
"\n";
for (int track = 0; track < spec.cylinders; track++)
{
for (int head = 0; head < spec.heads; head++)
{
for (int sectorId = 0; sectorId < spec.sectors; sectorId++)
{
f << fmt::format("{},{},", track, head);
const auto& sector = sectors.get(track, head, sectorId);
if (!sector)
f << fmt::format(",,{},,,,,,,,MISSING\n", sectorId);
else
f << fmt::format("{},{},{},{},{},{},{},{},{},{},{}\n",
sector->logicalTrack,
sector->logicalSide,
sector->logicalSector,
sector->clock,
sector->headerStartTime,
sector->headerEndTime,
sector->dataStartTime,
sector->dataEndTime,
sector->position.bytes,
sector->data.size(),
Sector::statusToString(sector->status)
);
}
}
}
}
void ImageWriter::printMap() void ImageWriter::printMap()
{ {
int badSectors = 0; int badSectors = 0;

View File

@@ -29,12 +29,15 @@ private:
const SectorSet& sectors, const ImageSpec& spec); const SectorSet& sectors, const ImageSpec& spec);
static std::unique_ptr<ImageWriter> createD64ImageWriter( static std::unique_ptr<ImageWriter> createD64ImageWriter(
const SectorSet& sectors, const ImageSpec& spec); const SectorSet& sectors, const ImageSpec& spec);
static std::unique_ptr<ImageWriter> createDiskCopyImageWriter(
const SectorSet& sectors, const ImageSpec& spec);
static Constructor findConstructor(const ImageSpec& spec); static Constructor findConstructor(const ImageSpec& spec);
public: public:
virtual void adjustGeometry(); virtual void adjustGeometry();
void printMap(); void printMap();
void writeCsv(const std::string& filename);
virtual void writeImage() = 0; virtual void writeImage() = 0;
protected: protected:

View File

@@ -1,5 +1,4 @@
#include "globals.h" #include "globals.h"
#include "image.h"
#include "flags.h" #include "flags.h"
#include "dataspec.h" #include "dataspec.h"
#include "sector.h" #include "sector.h"
@@ -47,7 +46,7 @@ public:
if (sector) if (sector)
{ {
outputFile.seekp(sector->logicalTrack*trackSize + sector->logicalSide*headSize + sector->logicalSector*numBytes, std::ios::beg); outputFile.seekp(sector->logicalTrack*trackSize + sector->logicalSide*headSize + sector->logicalSector*numBytes, std::ios::beg);
outputFile.write((const char*) sector->data.cbegin(), sector->data.size()); sector->data.slice(0, numBytes).writeTo(outputFile);
} }
} }
} }

View File

@@ -1,5 +1,4 @@
#include "globals.h" #include "globals.h"
#include "image.h"
#include "flags.h" #include "flags.h"
#include "dataspec.h" #include "dataspec.h"
#include "sector.h" #include "sector.h"

View File

@@ -1,6 +1,6 @@
#include "globals.h" #include "globals.h"
#include "flags.h" #include "flags.h"
#include "usb.h" #include "usb/usb.h"
#include "fluxsource/fluxsource.h" #include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h" #include "fluxsink/fluxsink.h"
#include "reader.h" #include "reader.h"
@@ -12,12 +12,13 @@
#include "sectorset.h" #include "sectorset.h"
#include "visualiser.h" #include "visualiser.h"
#include "record.h" #include "record.h"
#include "image.h"
#include "bytes.h" #include "bytes.h"
#include "decoders/rawbits.h" #include "decoders/rawbits.h"
#include "track.h" #include "track.h"
#include "imagewriter/imagewriter.h" #include "imagewriter/imagewriter.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <iostream>
#include <fstream>
FlagGroup readerFlags FlagGroup readerFlags
{ {
@@ -68,6 +69,11 @@ static SettableFlag highDensityFlag(
{ "--high-density", "--hd" }, { "--high-density", "--hd" },
"set the drive to high density mode"); "set the drive to high density mode");
static StringFlag csvFile(
{ "--write-csv" },
"write a CSV report of the disk state",
"");
static std::unique_ptr<FluxSink> outputFluxSink; static std::unique_ptr<FluxSink> outputFluxSink;
void setReaderDefaultSource(const std::string& source) void setReaderDefaultSource(const std::string& source)
@@ -85,13 +91,23 @@ void setReaderRevolutions(int revolutions)
setHardwareFluxSourceRevolutions(revolutions); setHardwareFluxSourceRevolutions(revolutions);
} }
static void writeSectorsToFile(const SectorSet& sectors, const ImageSpec& spec)
{
std::unique_ptr<ImageWriter> writer(ImageWriter::create(sectors, spec));
writer->adjustGeometry();
writer->printMap();
if (!csvFile.get().empty())
writer->writeCsv(csvFile.get());
writer->writeImage();
}
void Track::readFluxmap() void Track::readFluxmap()
{ {
std::cout << fmt::format("{0:>3}.{1}: ", physicalTrack, physicalSide) << std::flush; std::cout << fmt::format("{0:>3}.{1}: ", physicalTrack, physicalSide) << std::flush;
fluxmap = fluxsource->readFlux(physicalTrack, physicalSide); fluxmap = fluxsource->readFlux(physicalTrack, physicalSide);
std::cout << fmt::format( std::cout << fmt::format(
"{0} ms in {1} bytes\n", "{0} ms in {1} bytes\n",
int(fluxmap->duration()/1e6), fluxmap->duration()/1e6,
fluxmap->bytes()); fluxmap->bytes());
if (outputFluxSink) if (outputFluxSink)
outputFluxSink->writeFlux(physicalTrack, physicalSide, *fluxmap); outputFluxSink->writeFlux(physicalTrack, physicalSide, *fluxmap);
@@ -176,8 +192,6 @@ void readDiskCommand(AbstractDecoder& decoder)
decoder.decodeToSectors(*track); decoder.decodeToSectors(*track);
std::cout << " "; std::cout << " ";
if (!track->sectors.empty())
{
std::cout << fmt::format("{} records, {} sectors; ", std::cout << fmt::format("{} records, {} sectors; ",
track->rawrecords.size(), track->rawrecords.size(),
track->sectors.size()); track->sectors.size());
@@ -193,9 +207,12 @@ void readDiskCommand(AbstractDecoder& decoder)
} }
bool hasBadSectors = false; bool hasBadSectors = false;
std::set<unsigned> requiredSectors = decoder.requiredSectors(*track);
for (const auto& i : readSectors) for (const auto& i : readSectors)
{ {
const auto& sector = i.second; const auto& sector = i.second;
requiredSectors.erase(sector->logicalSector);
if (sector->status != Sector::OK) if (sector->status != Sector::OK)
{ {
std::cout << std::endl std::cout << std::endl
@@ -204,6 +221,12 @@ void readDiskCommand(AbstractDecoder& decoder)
hasBadSectors = true; hasBadSectors = true;
} }
} }
for (unsigned logicalSector : requiredSectors)
{
std::cout << "\n"
<< " Required sector " << logicalSector << " missing; ";
hasBadSectors = true;
}
if (hasBadSectors) if (hasBadSectors)
failures = false; failures = false;
@@ -213,7 +236,6 @@ void readDiskCommand(AbstractDecoder& decoder)
if (!hasBadSectors) if (!hasBadSectors)
break; break;
}
if (!track->fluxsource->retryable()) if (!track->fluxsource->retryable())
break; break;
@@ -239,13 +261,13 @@ void readDiskCommand(AbstractDecoder& decoder)
if (dumpSectors) if (dumpSectors)
{ {
std::cout << "\nDecoded sectors follow:\n\n"; std::cout << "\nDecoded sectors follow:\n\n";
for (auto& i : readSectors) for (auto& sector : track->sectors)
{ {
auto& sector = i.second; std::cout << fmt::format("{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: status {}\n",
std::cout << fmt::format("{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock\n", sector.logicalTrack, sector.logicalSide, sector.logicalSector,
sector->logicalTrack, sector->logicalSide, sector->logicalSector, sector.position.ns() / 1000.0, sector.clock / 1000.0,
sector->position.ns() / 1000.0, sector->clock / 1000.0); sector.status);
hexdump(std::cout, sector->data); hexdump(std::cout, sector.data);
std::cout << std::endl; std::cout << std::endl;
} }
} }
@@ -274,7 +296,7 @@ void readDiskCommand(AbstractDecoder& decoder)
if (!visualise.get().empty()) if (!visualise.get().empty())
visualiseSectorsToFile(allSectors, visualise.get()); visualiseSectorsToFile(allSectors, visualise.get());
writeSectorsToFile(allSectors, outputSpec); writeSectorsToFile(allSectors, outputSpec);
if (failures) if (failures)
std::cerr << "Warning: some sectors could not be decoded." << std::endl; std::cerr << "Warning: some sectors could not be decoded." << std::endl;

View File

@@ -1,5 +1,4 @@
#include "globals.h" #include "globals.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"

View File

@@ -10,8 +10,9 @@ enum
FLUX_VERSION_0, /* without properties table */ FLUX_VERSION_0, /* without properties table */
FLUX_VERSION_1, FLUX_VERSION_1,
FLUX_VERSION_2, /* new bytecode with index marks */ FLUX_VERSION_2, /* new bytecode with index marks */
FLUX_VERSION_3, /* simplified bytecode with six-bit timer */
FLUX_VERSION_CURRENT = 2, FLUX_VERSION_CURRENT = FLUX_VERSION_3,
}; };
extern void sqlCheck(sqlite3* db, int i); extern void sqlCheck(sqlite3* db, int i);

View File

@@ -1,343 +0,0 @@
#include "globals.h"
#include "usb.h"
#include "protocol.h"
#include "fluxmap.h"
#include "bytes.h"
#include "common/crunch.h"
#include <libusb.h>
#include "fmt/format.h"
#define TIMEOUT 5000
static libusb_device_handle* device;
static uint8_t buffer[FRAME_SIZE];
static std::string usberror(int i)
{
return libusb_strerror((libusb_error) i);
}
static void usb_init()
{
if (device)
return;
int i = libusb_init(NULL);
if (i < 0)
Error() << "could not start libusb: " << usberror(i);
device = libusb_open_device_with_vid_pid(NULL, FLUXENGINE_VID, FLUXENGINE_PID);
if (!device)
Error() << "cannot find the FluxEngine (is it plugged in?)";
int cfg = -1;
libusb_get_configuration(device, &cfg);
if (cfg != 1)
{
i = libusb_set_configuration(device, 1);
if (i < 0)
Error() << "the FluxEngine would not accept configuration: " << usberror(i);
}
i = libusb_claim_interface(device, 0);
if (i < 0)
Error() << "could not claim interface: " << usberror(i);
int version = usbGetVersion();
if (version != FLUXENGINE_VERSION)
Error() << "your FluxEngine firmware is at version " << version
<< " but the client is for version " << FLUXENGINE_VERSION
<< "; please upgrade";
}
static int usb_cmd_send(void* ptr, int len)
{
//std::cerr << "send:\n";
//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
int i = libusb_interrupt_transfer(device, FLUXENGINE_CMD_OUT_EP,
(uint8_t*) ptr, len, &len, TIMEOUT);
if (i < 0)
Error() << "failed to send command: " << usberror(i);
return len;
}
void usb_cmd_recv(void* ptr, int len)
{
int i = libusb_interrupt_transfer(device, FLUXENGINE_CMD_IN_EP,
(uint8_t*) ptr, len, &len, TIMEOUT);
if (i < 0)
Error() << "failed to receive command reply: " << usberror(i);
//std::cerr << "recv:\n";
//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
}
static void bad_reply(void)
{
struct error_frame* f = (struct error_frame*) buffer;
if (f->f.type != F_FRAME_ERROR)
Error() << fmt::format("bad USB reply 0x{:2x}", f->f.type);
switch (f->error)
{
case F_ERROR_BAD_COMMAND:
Error() << "device did not understand command";
case F_ERROR_UNDERRUN:
Error() << "USB underrun (not enough bandwidth)";
default:
Error() << fmt::format("unknown device error {}", f->error);
}
}
template <typename T>
static T* await_reply(int desired)
{
for (;;)
{
usb_cmd_recv(buffer, sizeof(buffer));
struct any_frame* r = (struct any_frame*) buffer;
if (r->f.type == F_FRAME_DEBUG)
{
std::cout << "dev: " << ((struct debug_frame*)r)->payload << std::endl;
continue;
}
if (r->f.type != desired)
bad_reply();
return (T*) r;
}
}
int usbGetVersion(void)
{
usb_init();
struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY);
return r->version;
}
void usbSeek(int track)
{
usb_init();
struct seek_frame f = {
{ .type = F_FRAME_SEEK_CMD, .size = sizeof(f) },
.track = (uint8_t) track
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_SEEK_REPLY);
}
void usbRecalibrate()
{
usb_init();
struct any_frame f = {
{ .type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f) },
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY);
}
nanoseconds_t usbGetRotationalPeriod(void)
{
usb_init();
struct any_frame f = { .f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY);
return r->period_ms * 1000000;
}
static int large_bulk_transfer(int ep, Bytes& bytes)
{
int len;
int i = libusb_bulk_transfer(device, ep, bytes.begin(), bytes.size(), &len, TIMEOUT);
if (i < 0)
Error() << "data transfer failed: " << usberror(i);
return len;
}
void usbTestBulkWrite()
{
usb_init();
struct any_frame f = { .f = {.type = F_FRAME_BULK_WRITE_TEST_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
/* These must match the device. */
const int XSIZE = 64;
const int YSIZE = 256;
const int ZSIZE = 64;
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
double start_time = getCurrentTime();
large_bulk_transfer(FLUXENGINE_DATA_IN_EP, bulk_buffer);
double elapsed_time = getCurrentTime() - start_time;
std::cout << "Transferred "
<< bulk_buffer.size()
<< " bytes from FluxEngine -> PC in "
<< int(elapsed_time * 1000.0)
<< " ms ("
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
<< " kB/s)"
<< std::endl;
for (int x=0; x<XSIZE; x++)
for (int y=0; y<YSIZE; y++)
for (int z=0; z<ZSIZE; z++)
{
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
if (bulk_buffer[offset] != uint8_t(x+y+z))
Error() << "data transfer corrupted at 0x"
<< std::hex << offset << std::dec
<< " "
<< x << '.' << y << '.' << z << '.';
}
await_reply<struct any_frame>(F_FRAME_BULK_WRITE_TEST_REPLY);
}
void usbTestBulkRead()
{
usb_init();
struct any_frame f = { .f = {.type = F_FRAME_BULK_READ_TEST_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
/* These must match the device. */
const int XSIZE = 64;
const int YSIZE = 256;
const int ZSIZE = 64;
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
for (int x=0; x<XSIZE; x++)
for (int y=0; y<YSIZE; y++)
for (int z=0; z<ZSIZE; z++)
{
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
bulk_buffer[offset] = uint8_t(x+y+z);
}
double start_time = getCurrentTime();
large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, bulk_buffer);
double elapsed_time = getCurrentTime() - start_time;
std::cout << "Transferred "
<< bulk_buffer.size()
<< " bytes from PC -> FluxEngine in "
<< int(elapsed_time * 1000.0)
<< " ms ("
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
<< " kB/s)"
<< std::endl;
await_reply<struct any_frame>(F_FRAME_BULK_READ_TEST_REPLY);
}
Bytes usbRead(int side, bool synced, nanoseconds_t readTime)
{
struct read_frame f = {
.f = { .type = F_FRAME_READ_CMD, .size = sizeof(f) },
.side = (uint8_t) side,
.synced = (uint8_t) synced
};
uint16_t milliseconds = readTime / 1e6;
((uint8_t*)&f.milliseconds)[0] = milliseconds;
((uint8_t*)&f.milliseconds)[1] = milliseconds >> 8;
usb_cmd_send(&f, f.f.size);
auto fluxmap = std::unique_ptr<Fluxmap>(new Fluxmap);
Bytes buffer(1024*1024);
int len = large_bulk_transfer(FLUXENGINE_DATA_IN_EP, buffer);
buffer.resize(len);
await_reply<struct any_frame>(F_FRAME_READ_REPLY);
return buffer;
}
void usbWrite(int side, const Bytes& bytes)
{
unsigned safelen = bytes.size() & ~(FRAME_SIZE-1);
Bytes safeBytes = bytes.slice(0, safelen);
struct write_frame f = {
.f = { .type = F_FRAME_WRITE_CMD, .size = sizeof(f) },
.side = (uint8_t) side,
};
((uint8_t*)&f.bytes_to_write)[0] = safelen;
((uint8_t*)&f.bytes_to_write)[1] = safelen >> 8;
((uint8_t*)&f.bytes_to_write)[2] = safelen >> 16;
((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24;
usb_cmd_send(&f, f.f.size);
large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, safeBytes);
await_reply<struct any_frame>(F_FRAME_WRITE_REPLY);
}
void usbErase(int side)
{
struct erase_frame f = {
.f = { .type = F_FRAME_ERASE_CMD, .size = sizeof(f) },
.side = (uint8_t) side,
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_ERASE_REPLY);
}
void usbSetDrive(int drive, bool high_density, int index_mode)
{
usb_init();
struct set_drive_frame f = {
{ .type = F_FRAME_SET_DRIVE_CMD, .size = sizeof(f) },
.drive = (uint8_t) drive,
.high_density = high_density,
.index_mode = (uint8_t) index_mode
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_SET_DRIVE_REPLY);
}
/* Hacky: the board always operates in little-endian mode. */
static uint16_t read_short_from_usb(uint16_t usb)
{
uint8_t* p = (uint8_t*)&usb;
return p[0] | (p[1] << 8);
}
static void convert_voltages_from_usb(const struct voltages& vin, struct voltages& vout)
{
vout.logic0_mv = read_short_from_usb(vin.logic0_mv);
vout.logic1_mv = read_short_from_usb(vin.logic1_mv);
}
void usbMeasureVoltages(struct voltages_frame* voltages)
{
usb_init();
struct any_frame f = {
{ .type = F_FRAME_MEASURE_VOLTAGES_CMD, .size = sizeof(f) },
};
usb_cmd_send(&f, f.f.size);
struct voltages_frame* r = await_reply<struct voltages_frame>(F_FRAME_MEASURE_VOLTAGES_REPLY);
convert_voltages_from_usb(r->input_both_off, voltages->input_both_off);
convert_voltages_from_usb(r->input_drive_0_selected, voltages->input_drive_0_selected);
convert_voltages_from_usb(r->input_drive_1_selected, voltages->input_drive_1_selected);
convert_voltages_from_usb(r->input_drive_0_running, voltages->input_drive_0_running);
convert_voltages_from_usb(r->input_drive_1_running, voltages->input_drive_1_running);
convert_voltages_from_usb(r->output_both_off, voltages->output_both_off);
convert_voltages_from_usb(r->output_drive_0_selected, voltages->output_drive_0_selected);
convert_voltages_from_usb(r->output_drive_1_selected, voltages->output_drive_1_selected);
convert_voltages_from_usb(r->output_drive_0_running, voltages->output_drive_0_running);
convert_voltages_from_usb(r->output_drive_1_running, voltages->output_drive_1_running);
}

View File

@@ -1,19 +0,0 @@
#ifndef USB_H
#define USB_H
class Fluxmap;
class Bytes;
extern int usbGetVersion();
extern void usbRecalibrate();
extern void usbSeek(int track);
extern nanoseconds_t usbGetRotationalPeriod();
extern void usbTestBulkWrite();
extern void usbTestBulkRead();
extern Bytes usbRead(int side, bool synced, nanoseconds_t readTime);
extern void usbWrite(int side, const Bytes& bytes);
extern void usbErase(int side);
extern void usbSetDrive(int drive, bool high_density, int index_mode);
extern void usbMeasureVoltages(struct voltages_frame* voltages);
#endif

326
lib/usb/fluxengineusb.cc Normal file
View File

@@ -0,0 +1,326 @@
#include "globals.h"
#include "usb.h"
#include "protocol.h"
#include "fluxmap.h"
#include "bytes.h"
#include <libusb.h>
#include "fmt/format.h"
#define TIMEOUT 5000
/* Hacky: the board always operates in little-endian mode. */
static uint16_t read_short_from_usb(uint16_t usb)
{
uint8_t* p = (uint8_t*)&usb;
return p[0] | (p[1] << 8);
}
class FluxEngineUsb : public USB
{
private:
uint8_t _buffer[FRAME_SIZE];
int usb_cmd_send(void* ptr, int len)
{
//std::cerr << "send:\n";
//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
int i = libusb_interrupt_transfer(_device, FLUXENGINE_CMD_OUT_EP,
(uint8_t*) ptr, len, &len, TIMEOUT);
if (i < 0)
Error() << "failed to send command: " << usberror(i);
return len;
}
void usb_cmd_recv(void* ptr, int len)
{
int i = libusb_interrupt_transfer(_device, FLUXENGINE_CMD_IN_EP,
(uint8_t*) ptr, len, &len, TIMEOUT);
if (i < 0)
Error() << "failed to receive command reply: " << usberror(i);
//std::cerr << "recv:\n";
//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
}
int large_bulk_transfer(int ep, Bytes& bytes)
{
if (bytes.size() == 0)
return 0;
int len;
int i = libusb_bulk_transfer(_device, ep, bytes.begin(), bytes.size(), &len, TIMEOUT);
if (i < 0)
Error() << fmt::format("data transfer failed at {} bytes: {}", len, usberror(i));
return len;
}
public:
FluxEngineUsb(libusb_device_handle* device)
{
_device = device;
int i;
int cfg = -1;
libusb_get_configuration(_device, &cfg);
if (cfg != 1)
{
i = libusb_set_configuration(_device, 1);
if (i < 0)
Error() << "the FluxEngine would not accept configuration: " << usberror(i);
}
i = libusb_claim_interface(_device, 0);
if (i < 0)
Error() << "could not claim interface: " << usberror(i);
int version = getVersion();
if (version != FLUXENGINE_VERSION)
Error() << "your FluxEngine firmware is at version " << version
<< " but the client is for version " << FLUXENGINE_VERSION
<< "; please upgrade";
}
private:
void bad_reply(void)
{
struct error_frame* f = (struct error_frame*) _buffer;
if (f->f.type != F_FRAME_ERROR)
Error() << fmt::format("bad USB reply 0x{:2x}", f->f.type);
switch (f->error)
{
case F_ERROR_BAD_COMMAND:
Error() << "device did not understand command";
case F_ERROR_UNDERRUN:
Error() << "USB underrun (not enough bandwidth)";
default:
Error() << fmt::format("unknown device error {}", f->error);
}
}
template <typename T>
T* await_reply(int desired)
{
for (;;)
{
usb_cmd_recv(_buffer, sizeof(_buffer));
struct any_frame* r = (struct any_frame*) _buffer;
if (r->f.type == F_FRAME_DEBUG)
{
std::cout << "dev: " << ((struct debug_frame*)r)->payload << std::endl;
continue;
}
if (r->f.type != desired)
bad_reply();
return (T*) r;
}
}
public:
int getVersion()
{
struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY);
return r->version;
}
void seek(int track)
{
struct seek_frame f = {
{ .type = F_FRAME_SEEK_CMD, .size = sizeof(f) },
.track = (uint8_t) track
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_SEEK_REPLY);
}
void recalibrate()
{
struct any_frame f = {
{ .type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f) },
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY);
}
nanoseconds_t getRotationalPeriod(void)
{
struct any_frame f = { .f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY);
return r->period_ms * 1000000;
}
void testBulkWrite()
{
struct any_frame f = { .f = {.type = F_FRAME_BULK_WRITE_TEST_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
/* These must match the device. */
const int XSIZE = 64;
const int YSIZE = 256;
const int ZSIZE = 64;
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
double start_time = getCurrentTime();
large_bulk_transfer(FLUXENGINE_DATA_IN_EP, bulk_buffer);
double elapsed_time = getCurrentTime() - start_time;
std::cout << "Transferred "
<< bulk_buffer.size()
<< " bytes from FluxEngine -> PC in "
<< int(elapsed_time * 1000.0)
<< " ms ("
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
<< " kB/s)"
<< std::endl;
for (int x=0; x<XSIZE; x++)
for (int y=0; y<YSIZE; y++)
for (int z=0; z<ZSIZE; z++)
{
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
if (bulk_buffer[offset] != uint8_t(x+y+z))
Error() << "data transfer corrupted at 0x"
<< std::hex << offset << std::dec
<< " "
<< x << '.' << y << '.' << z << '.';
}
await_reply<struct any_frame>(F_FRAME_BULK_WRITE_TEST_REPLY);
}
void testBulkRead()
{
struct any_frame f = { .f = {.type = F_FRAME_BULK_READ_TEST_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
/* These must match the device. */
const int XSIZE = 64;
const int YSIZE = 256;
const int ZSIZE = 64;
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
for (int x=0; x<XSIZE; x++)
for (int y=0; y<YSIZE; y++)
for (int z=0; z<ZSIZE; z++)
{
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
bulk_buffer[offset] = uint8_t(x+y+z);
}
double start_time = getCurrentTime();
large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, bulk_buffer);
double elapsed_time = getCurrentTime() - start_time;
std::cout << "Transferred "
<< bulk_buffer.size()
<< " bytes from PC -> FluxEngine in "
<< int(elapsed_time * 1000.0)
<< " ms ("
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
<< " kB/s)"
<< std::endl;
await_reply<struct any_frame>(F_FRAME_BULK_READ_TEST_REPLY);
}
Bytes read(int side, bool synced, nanoseconds_t readTime)
{
struct read_frame f = {
.f = { .type = F_FRAME_READ_CMD, .size = sizeof(f) },
.side = (uint8_t) side,
.synced = (uint8_t) synced
};
uint16_t milliseconds = readTime / 1e6;
((uint8_t*)&f.milliseconds)[0] = milliseconds;
((uint8_t*)&f.milliseconds)[1] = milliseconds >> 8;
usb_cmd_send(&f, f.f.size);
auto fluxmap = std::unique_ptr<Fluxmap>(new Fluxmap);
Bytes buffer(1024*1024);
int len = large_bulk_transfer(FLUXENGINE_DATA_IN_EP, buffer);
buffer.resize(len);
await_reply<struct any_frame>(F_FRAME_READ_REPLY);
return buffer;
}
void write(int side, const Bytes& bytes)
{
unsigned safelen = bytes.size() & ~(FRAME_SIZE-1);
Bytes safeBytes = bytes.slice(0, safelen);
struct write_frame f = {
.f = { .type = F_FRAME_WRITE_CMD, .size = sizeof(f) },
.side = (uint8_t) side,
};
((uint8_t*)&f.bytes_to_write)[0] = safelen;
((uint8_t*)&f.bytes_to_write)[1] = safelen >> 8;
((uint8_t*)&f.bytes_to_write)[2] = safelen >> 16;
((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24;
usb_cmd_send(&f, f.f.size);
large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, safeBytes);
await_reply<struct any_frame>(F_FRAME_WRITE_REPLY);
}
void erase(int side)
{
struct erase_frame f = {
.f = { .type = F_FRAME_ERASE_CMD, .size = sizeof(f) },
.side = (uint8_t) side,
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_ERASE_REPLY);
}
void setDrive(int drive, bool high_density, int index_mode)
{
struct set_drive_frame f = {
{ .type = F_FRAME_SET_DRIVE_CMD, .size = sizeof(f) },
.drive = (uint8_t) drive,
.high_density = high_density,
.index_mode = (uint8_t) index_mode
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_SET_DRIVE_REPLY);
}
void measureVoltages(struct voltages_frame* voltages)
{
struct any_frame f = {
{ .type = F_FRAME_MEASURE_VOLTAGES_CMD, .size = sizeof(f) },
};
usb_cmd_send(&f, f.f.size);
auto convert_voltages_from_usb = [&](const struct voltages& vin, struct voltages& vout)
{
vout.logic0_mv = read_short_from_usb(vin.logic0_mv);
vout.logic1_mv = read_short_from_usb(vin.logic1_mv);
};
struct voltages_frame* r = await_reply<struct voltages_frame>(F_FRAME_MEASURE_VOLTAGES_REPLY);
convert_voltages_from_usb(r->input_both_off, voltages->input_both_off);
convert_voltages_from_usb(r->input_drive_0_selected, voltages->input_drive_0_selected);
convert_voltages_from_usb(r->input_drive_1_selected, voltages->input_drive_1_selected);
convert_voltages_from_usb(r->input_drive_0_running, voltages->input_drive_0_running);
convert_voltages_from_usb(r->input_drive_1_running, voltages->input_drive_1_running);
convert_voltages_from_usb(r->output_both_off, voltages->output_both_off);
convert_voltages_from_usb(r->output_drive_0_selected, voltages->output_drive_0_selected);
convert_voltages_from_usb(r->output_drive_1_selected, voltages->output_drive_1_selected);
convert_voltages_from_usb(r->output_drive_0_running, voltages->output_drive_0_running);
convert_voltages_from_usb(r->output_drive_1_running, voltages->output_drive_1_running);
}
};
USB* createFluxengineUsb(libusb_device_handle* device)
{
return new FluxEngineUsb(device);
}

139
lib/usb/usb.cc Normal file
View File

@@ -0,0 +1,139 @@
#include "globals.h"
#include "flags.h"
#include "usb.h"
#include "protocol.h"
#include "fluxmap.h"
#include "bytes.h"
#include <libusb.h>
#include "fmt/format.h"
FlagGroup usbFlags;
static StringFlag device(
{ "--device" },
"serial number of hardware device to use",
"");
static USB* usb = NULL;
enum
{
DEV_FLUXENGINE,
};
struct CandidateDevice
{
libusb_device* device;
libusb_device_descriptor desc;
int type;
std::string serial;
};
USB::~USB()
{}
std::string USB::usberror(int i)
{
return libusb_strerror((libusb_error) i);
}
static const char* device_type(int i)
{
switch (i)
{
case DEV_FLUXENGINE: return "FluxEngine";
default: assert(false);
}
}
static std::map<std::string, std::unique_ptr<CandidateDevice>> get_candidates(libusb_device** devices, int numdevices)
{
std::map<std::string, std::unique_ptr<CandidateDevice>> candidates;
for (int i=0; i<numdevices; i++)
{
std::unique_ptr<CandidateDevice> candidate(new CandidateDevice());
candidate->device = devices[i];
(void) libusb_get_device_descriptor(devices[i], &candidate->desc);
if ((candidate->desc.idVendor == FLUXENGINE_VID) && (candidate->desc.idProduct == FLUXENGINE_PID))
{
candidate->type = DEV_FLUXENGINE;
candidate->serial = "";
libusb_device_handle* handle;
if (libusb_open(candidate->device, &handle) == 0)
{
unsigned char buffer[64];
libusb_get_string_descriptor_ascii(handle,
candidate->desc.iSerialNumber, buffer, sizeof(buffer));
candidate->serial = (const char*) buffer;
libusb_close(handle);
}
candidates[candidate->serial] = std::move(candidate);
}
}
return candidates;
}
static void open_device(CandidateDevice& candidate)
{
libusb_device_handle* handle;
int i = libusb_open(candidate.device, &handle);
if (i < 0)
Error() << "cannot open USB device: " << libusb_strerror((libusb_error) i);
std::cout << "Using " << device_type(candidate.type) << " with serial number " << candidate.serial << '\n';
usb = createFluxengineUsb(handle);
}
static CandidateDevice& select_candidate(const std::map<std::string, std::unique_ptr<CandidateDevice>>& devices)
{
if (devices.size() == 0)
Error() << "no USB devices found (is one plugged in? Do you have permission to access USB devices?)";
if (device.get() == "")
{
if (devices.size() == 1)
return *devices.begin()->second;
std::cout << "More than one USB device detected. Use --device to specify which one to use:\n";
for (auto& i : devices)
std::cout << " " << device_type(i.second->type) << ": " << i.first << '\n';
Error() << "specify USB device";
}
else
{
const auto& i = devices.find(device);
if (i != devices.end())
return *i->second;
Error() << "device with serial number '" << device.get() << "' not found";
}
}
USB& getUsb()
{
if (!usb)
{
int i = libusb_init(NULL);
if (i < 0)
Error() << "could not start libusb: " << libusb_strerror((libusb_error) i);
libusb_device** devices;
int numdevices = libusb_get_device_list(NULL, &devices);
if (numdevices < 0)
Error() << "could not enumerate USB bus: " << libusb_strerror((libusb_error) numdevices);
auto candidates = get_candidates(devices, numdevices);
auto candidate = select_candidate(candidates);
open_device(candidate);
libusb_free_device_list(devices, true);
}
return *usb;
}

60
lib/usb/usb.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef USB_H
#define USB_H
#include "bytes.h"
#include "flags.h"
class Fluxmap;
class libusb_device_handle;
class USB
{
public:
virtual ~USB();
virtual int getVersion() = 0;
virtual void recalibrate() = 0;
virtual void seek(int track) = 0;
virtual nanoseconds_t getRotationalPeriod() = 0;
virtual void testBulkWrite() = 0;
virtual void testBulkRead() = 0;
virtual Bytes read(int side, bool synced, nanoseconds_t readTime) = 0;
virtual void write(int side, const Bytes& bytes) = 0;
virtual void erase(int side) = 0;
virtual void setDrive(int drive, bool high_density, int index_mode) = 0;
virtual void measureVoltages(struct voltages_frame* voltages) = 0;
protected:
std::string usberror(int i);
libusb_device_handle* _device;
};
extern FlagGroup usbFlags;
extern USB& getUsb();
extern USB* createFluxengineUsb(libusb_device_handle* device);
static inline int usbGetVersion() { return getUsb().getVersion(); }
static inline void usbRecalibrate() { getUsb().recalibrate(); }
static inline void usbSeek(int track) { getUsb().seek(track); }
static inline void usbTestBulkWrite() { getUsb().testBulkWrite(); }
static inline void usbTestBulkRead() { getUsb().testBulkRead(); }
static inline void usbErase(int side) { getUsb().erase(side); }
static inline nanoseconds_t usbGetRotationalPeriod()
{ return getUsb().getRotationalPeriod(); }
static inline Bytes usbRead(int side, bool synced, nanoseconds_t readTime)
{ return getUsb().read(side, synced, readTime); }
static inline void usbWrite(int side, const Bytes& bytes)
{ getUsb().write(side, bytes); }
static inline void usbSetDrive(int drive, bool high_density, int index_mode)
{ getUsb().setDrive(drive, high_density, index_mode); }
static inline void usbMeasureVoltages(struct voltages_frame* voltages)
{ getUsb().measureVoltages(voltages); }
#endif

View File

@@ -1,6 +1,5 @@
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include "globals.h" #include "globals.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "visualiser.h" #include "visualiser.h"
@@ -48,8 +47,8 @@ void visualiseSectorsToFile(const SectorSet& sectors, const std::string& filenam
auto drawArc = [&](const std::unique_ptr<Sector>& sector, nanoseconds_t start, nanoseconds_t end, const std::string& colour) auto drawArc = [&](const std::unique_ptr<Sector>& sector, nanoseconds_t start, nanoseconds_t end, const std::string& colour)
{ {
start %= period*1000000; start = fmod(start, period*1000000.0);
end %= period*1000000; end = fmod(end, period*1000000.0);
if (end < start) if (end < start)
end += period*1000000; end += period*1000000;

View File

@@ -4,18 +4,18 @@
#include "writer.h" #include "writer.h"
#include "sql.h" #include "sql.h"
#include "protocol.h" #include "protocol.h"
#include "usb.h" #include "usb/usb.h"
#include "dataspec.h" #include "dataspec.h"
#include "encoders/encoders.h" #include "encoders/encoders.h"
#include "fluxsource/fluxsource.h" #include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h" #include "fluxsink/fluxsink.h"
#include "imagereader/imagereader.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "record.h" #include "record.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
FlagGroup writerFlags { &hardwareFluxSourceFlags, &hardwareFluxSinkFlags }; FlagGroup writerFlags { &hardwareFluxSourceFlags, &sqliteFluxSinkFlags, &hardwareFluxSinkFlags };
static DataSpecFlag dest( static DataSpecFlag dest(
{ "--dest", "-d" }, { "--dest", "-d" },
@@ -43,6 +43,11 @@ void setWriterDefaultInput(const std::string& input)
::input.set(input); ::input.set(input);
} }
static SectorSet readSectorsFromFile(const ImageSpec& spec)
{
return ImageReader::create(spec)->readImage();
}
void writeTracks( void writeTracks(
const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer) const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer)
{ {
@@ -53,19 +58,7 @@ void writeTracks(
setHardwareFluxSourceDensity(highDensityFlag); setHardwareFluxSourceDensity(highDensityFlag);
setHardwareFluxSinkDensity(highDensityFlag); setHardwareFluxSinkDensity(highDensityFlag);
if (!spec.filename.empty()) std::shared_ptr<FluxSink> fluxSink = FluxSink::create(spec);
{
outdb = sqlOpen(spec.filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
sqlPrepareFlux(outdb);
sqlStmt(outdb, "BEGIN;");
sqlWriteIntProperty(outdb, "version", FLUX_VERSION_CURRENT);
atexit([]()
{
sqlStmt(outdb, "COMMIT;");
sqlClose(outdb);
}
);
}
for (const auto& location : spec.locations) for (const auto& location : spec.locations)
{ {
@@ -73,31 +66,16 @@ void writeTracks(
std::unique_ptr<Fluxmap> fluxmap = producer(location.track, location.side); std::unique_ptr<Fluxmap> fluxmap = producer(location.track, location.side);
if (!fluxmap) if (!fluxmap)
{ {
if (!outdb) /* Create an empty fluxmap for writing. */
{ fluxmap.reset(new Fluxmap());
std::cout << "erasing\n";
usbSeek(location.track);
usbErase(location.side);
}
else
std::cout << "skipping\n";
}
else
{
/* Precompensation actually seems to make things worse, so let's leave
* it disabled for now. */
//fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2);
if (outdb)
sqlWriteFlux(outdb, location.track, location.side, *fluxmap);
else
{
Bytes crunched = fluxmap->rawBytes().crunch();
usbSeek(location.track);
usbWrite(location.side, crunched);
}
std::cout << fmt::format(
"{0} ms in {1} bytes", int(fluxmap->duration()/1e6), fluxmap->bytes()) << std::endl;
} }
/* Precompensation actually seems to make things worse, so let's leave
* it disabled for now. */
//fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2);
fluxSink->writeFlux(location.track, location.side, *fluxmap);
std::cout << fmt::format(
"{0} ms in {1} bytes", int(fluxmap->duration()/1e6), fluxmap->bytes()) << std::endl;
} }
} }

View File

@@ -154,6 +154,7 @@ buildlibrary libbackend.a \
lib/imagereader/imagereader.cc \ lib/imagereader/imagereader.cc \
lib/imagereader/imgimagereader.cc \ lib/imagereader/imgimagereader.cc \
lib/imagewriter/d64imagewriter.cc \ lib/imagewriter/d64imagewriter.cc \
lib/imagewriter/diskcopyimagewriter.cc \
lib/imagewriter/imagewriter.cc \ lib/imagewriter/imagewriter.cc \
lib/imagewriter/imgimagewriter.cc \ lib/imagewriter/imgimagewriter.cc \
lib/imagewriter/ldbsimagewriter.cc \ lib/imagewriter/ldbsimagewriter.cc \
@@ -174,7 +175,6 @@ buildlibrary libbackend.a \
arch/victor9k/decoder.cc \ arch/victor9k/decoder.cc \
arch/zilogmcz/decoder.cc \ arch/zilogmcz/decoder.cc \
lib/bytes.cc \ lib/bytes.cc \
lib/common/crunch.c \
lib/crc.cc \ lib/crc.cc \
lib/dataspec.cc \ lib/dataspec.cc \
lib/decoders/decoders.cc \ lib/decoders/decoders.cc \
@@ -191,15 +191,15 @@ buildlibrary libbackend.a \
lib/fluxsource/kryoflux.cc \ lib/fluxsource/kryoflux.cc \
lib/fluxsource/sqlitefluxsource.cc \ lib/fluxsource/sqlitefluxsource.cc \
lib/fluxsource/streamfluxsource.cc \ lib/fluxsource/streamfluxsource.cc \
lib/usb/usb.cc \
lib/usb/fluxengineusb.cc \
lib/globals.cc \ lib/globals.cc \
lib/hexdump.cc \ lib/hexdump.cc \
lib/image.cc \
lib/ldbs.cc \ lib/ldbs.cc \
lib/reader.cc \ lib/reader.cc \
lib/sector.cc \ lib/sector.cc \
lib/sectorset.cc \ lib/sectorset.cc \
lib/sql.cc \ lib/sql.cc \
lib/usb.cc \
lib/visualiser.cc \ lib/visualiser.cc \
lib/writer.cc \ lib/writer.cc \
@@ -263,7 +263,6 @@ buildsimpleprogram brother240tool \
runtest bitaccumulator-test tests/bitaccumulator.cc runtest bitaccumulator-test tests/bitaccumulator.cc
runtest bytes-test tests/bytes.cc runtest bytes-test tests/bytes.cc
runtest compression-test tests/compression.cc runtest compression-test tests/compression.cc
runtest crunch-test tests/crunch.cc
runtest dataspec-test tests/dataspec.cc runtest dataspec-test tests/dataspec.cc
runtest flags-test tests/flags.cc runtest flags-test tests/flags.cc
runtest fluxpattern-test tests/fluxpattern.cc runtest fluxpattern-test tests/fluxpattern.cc

View File

@@ -3,7 +3,7 @@
enum enum
{ {
FLUXENGINE_VERSION = 12, FLUXENGINE_VERSION = 14,
FLUXENGINE_VID = 0x1209, FLUXENGINE_VID = 0x1209,
FLUXENGINE_PID = 0x6e00, FLUXENGINE_PID = 0x6e00,
@@ -86,8 +86,8 @@ enum
enum enum
{ {
F_OP_PULSE = 0x80, F_BIT_PULSE = 0x80,
F_OP_INDEX = 0x81 F_BIT_INDEX = 0x40
}; };
struct frame_header struct frame_header

View File

@@ -81,14 +81,14 @@ int mainConvertFluxToAu(int argc, const char* argv[])
while (!fmr.eof()) while (!fmr.eof())
{ {
unsigned ticks; unsigned ticks;
int op = fmr.readOpcode(ticks); uint8_t bits = fmr.getNextEvent(ticks);
if (op == -1) if (fmr.eof())
break; break;
timestamp += ticks; timestamp += ticks;
if (op == F_OP_PULSE) if (bits & F_BIT_PULSE)
data[timestamp*channels + 0] = 0x7f; data[timestamp*channels + 0] = 0x7f;
if (withIndex && (op == F_OP_INDEX)) if (withIndex && (bits & F_BIT_INDEX))
data[timestamp*channels + 1] = 0x7f; data[timestamp*channels + 1] = 0x7f;
} }

View File

@@ -18,6 +18,11 @@ static SettableFlag fortyTrackMode(
"set 48 tpi mode; only every other physical track is emitted" "set 48 tpi mode; only every other physical track is emitted"
); );
static SettableFlag indexedMode(
{ "--indexed", "-i" },
"align data to track boundaries"
);
static SettableFlag singleSided( static SettableFlag singleSided(
{ "--single-sided", "-s" }, { "--single-sided", "-s" },
"only emit side 0" "only emit side 0"
@@ -45,14 +50,18 @@ static void write_le32(uint8_t dest[4], uint32_t v)
dest[3] = v >> 24; dest[3] = v >> 24;
} }
static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
{
ByteReader br(bytes);
while (!br.eof())
checksum += br.read_8();
}
static int strackno(int track, int side) static int strackno(int track, int side)
{ {
if (fortyTrackMode) if (fortyTrackMode)
track /= 2; track /= 2;
if (singleSided) return (track << 1) | side;
return track;
else
return (track << 1) | side;
} }
int mainConvertFluxToScp(int argc, const char* argv[]) int mainConvertFluxToScp(int argc, const char* argv[])
@@ -90,7 +99,8 @@ int mainConvertFluxToScp(int argc, const char* argv[])
fileheader.revolutions = 5; fileheader.revolutions = 5;
fileheader.start_track = 0; fileheader.start_track = 0;
fileheader.end_track = maxStrack; fileheader.end_track = maxStrack;
fileheader.flags = SCP_FLAG_INDEXED | (fortyTrackMode ? 0 : SCP_FLAG_96TPI); fileheader.flags = (indexedMode ? SCP_FLAG_INDEXED : 0)
| (fortyTrackMode ? 0 : SCP_FLAG_96TPI);
fileheader.cell_width = 0; fileheader.cell_width = 0;
fileheader.heads = singleSided ? 1 : 0; fileheader.heads = singleSided ? 1 : 0;
@@ -104,9 +114,15 @@ int mainConvertFluxToScp(int argc, const char* argv[])
for (int side = 0; side <= maxside; side++) for (int side = 0; side <= maxside; side++)
{ {
int strack = strackno(track, side); int strack = strackno(track, side);
std::cout << fmt::format("FE track {}.{}, SCP track {}: ", track, side, strack) << std::flush; std::cout << fmt::format("{}.{}: ", track, side) << std::flush;
auto fluxmap = sqlReadFlux(inputDb, track, side); auto fluxmap = sqlReadFlux(inputDb, track, side);
if (fluxmap->bytes() == 0)
{
std::cout << "missing\n";
continue;
}
ScpTrack trackheader = {0}; ScpTrack trackheader = {0};
trackheader.track_id[0] = 'T'; trackheader.track_id[0] = 'T';
trackheader.track_id[1] = 'R'; trackheader.track_id[1] = 'R';
@@ -117,6 +133,9 @@ int mainConvertFluxToScp(int argc, const char* argv[])
Bytes fluxdata; Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata); ByteWriter fluxdataWriter(fluxdata);
if (indexedMode)
fmr.findEvent(F_BIT_INDEX);
int revolution = 0; int revolution = 0;
unsigned revTicks = 0; unsigned revTicks = 0;
unsigned totalTicks = 0; unsigned totalTicks = 0;
@@ -125,50 +144,41 @@ int mainConvertFluxToScp(int argc, const char* argv[])
while (revolution < 5) while (revolution < 5)
{ {
unsigned ticks; unsigned ticks;
int opcode = fmr.readOpcode(ticks); uint8_t bits = fmr.getNextEvent(ticks);
if (ticks) ticksSinceLastPulse += ticks;
{ totalTicks += ticks;
ticksSinceLastPulse += ticks; revTicks += ticks;
totalTicks += ticks;
revTicks += ticks;
}
switch (opcode) if (fmr.eof() || (bits & F_BIT_INDEX))
{ {
case -1: /* end of flux, treat like an index marker */ auto* revheader = &trackheader.revolution[revolution];
case F_OP_INDEX: write_le32(revheader->offset, startOffset + sizeof(ScpTrack));
{ write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2);
auto* revheader = &trackheader.revolution[revolution]; write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
write_le32(revheader->offset, startOffset + sizeof(ScpTrack)); revolution++;
write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2); revheader++;
write_le32(revheader->index, revTicks * NS_PER_TICK / 25); revTicks = 0;
revolution++; startOffset = fluxdataWriter.pos;
revheader++; }
revTicks = 0;
startOffset = fluxdataWriter.pos;
break;
}
case F_OP_PULSE: if (bits & F_BIT_PULSE)
{ {
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25; unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000) while (t >= 0x10000)
{ {
fluxdataWriter.write_be16(0); fluxdataWriter.write_be16(0);
t -= 0x10000; t -= 0x10000;
} }
fluxdataWriter.write_be16(t); fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0; ticksSinceLastPulse = 0;
break; }
}
}
} }
write_le32(fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader)); write_le32(fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader)); trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
trackdataWriter += fluxdata; trackdataWriter += fluxdata;
std::cout << fmt::format("{} ms in {} bytes\n", std::cout << fmt::format("{:.3f} ms in {} bytes\n",
totalTicks * MS_PER_TICK, totalTicks * MS_PER_TICK,
fluxdata.size()); fluxdata.size());
} }
@@ -176,6 +186,13 @@ int mainConvertFluxToScp(int argc, const char* argv[])
sqlClose(inputDb); sqlClose(inputDb);
uint32_t checksum = 0;
appendChecksum(checksum,
Bytes((const uint8_t*) &fileheader, sizeof(fileheader))
.slice(0x10));
appendChecksum(checksum, trackdata);
write_le32(fileheader.checksum, checksum);
std::cout << "Writing output file...\n"; std::cout << "Writing output file...\n";
std::ofstream of(filenames[1], std::ios::out | std::ios::binary); std::ofstream of(filenames[1], std::ios::out | std::ios::binary);
if (!of.is_open()) if (!of.is_open())

View File

@@ -59,8 +59,8 @@ int mainConvertFluxToVcd(int argc, const char* argv[])
while (!fmr.eof()) while (!fmr.eof())
{ {
unsigned ticks; unsigned ticks;
int op = fmr.readOpcode(ticks); uint8_t bits = fmr.getNextEvent(ticks);
if (op == -1) if (fmr.eof())
break; break;
unsigned newtimestamp = timestamp + ticks; unsigned newtimestamp = timestamp + ticks;
@@ -71,9 +71,9 @@ int mainConvertFluxToVcd(int argc, const char* argv[])
of << fmt::format("#{} ", (uint64_t)(timestamp * NS_PER_TICK)); of << fmt::format("#{} ", (uint64_t)(timestamp * NS_PER_TICK));
} }
if (op == F_OP_PULSE) if (bits & F_BIT_PULSE)
of << "1p "; of << "1p ";
if (op == F_OP_INDEX) if (bits & F_BIT_INDEX)
of << "1i "; of << "1i ";
lasttimestamp = timestamp; lasttimestamp = timestamp;

View File

@@ -4,7 +4,6 @@
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "protocol.h" #include "protocol.h"
#include "decoders/rawbits.h" #include "decoders/rawbits.h"
#include "record.h" #include "record.h"
@@ -73,7 +72,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
while (!fr.eof()) while (!fr.eof())
{ {
unsigned interval = fr.readNextMatchingOpcode(F_OP_PULSE); unsigned interval = fr.findEvent(F_BIT_PULSE);
if (interval > 0xff) if (interval > 0xff)
continue; continue;
buckets[interval]++; buckets[interval]++;
@@ -156,7 +155,11 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
s += BLOCK_ELEMENTS[8]; s += BLOCK_ELEMENTS[8];
s += BLOCK_ELEMENTS[bar & 7]; s += BLOCK_ELEMENTS[bar & 7];
std::cout << fmt::format("{:.2f} {:6} {}", (double)i * US_PER_TICK, value, s); std::cout << fmt::format("{: 3} {:.2f} {:6} {}",
i,
(double)i * US_PER_TICK,
value,
s);
std::cout << std::endl; std::cout << std::endl;
} }
} }
@@ -186,6 +189,12 @@ int mainInspect(int argc, const char* argv[])
auto& track = *tracks.begin(); auto& track = *tracks.begin();
track->readFluxmap(); track->readFluxmap();
std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n",
track->fluxmap->bytes(),
track->fluxmap->duration() / 1e6);
std::cout << fmt::format("Required USB bandwidth: {}kB/s\n",
track->fluxmap->bytes()/1024.0 / (track->fluxmap->duration() / 1e9));
nanoseconds_t clockPeriod = guessClock(*track->fluxmap); nanoseconds_t clockPeriod = guessClock(*track->fluxmap);
std::cout << fmt::format("{:.2f} us clock detected.", (double)clockPeriod/1000.0) << std::flush; std::cout << fmt::format("{:.2f} us clock detected.", (double)clockPeriod/1000.0) << std::flush;
@@ -209,7 +218,7 @@ int mainInspect(int argc, const char* argv[])
nanoseconds_t lasttransition = 0; nanoseconds_t lasttransition = 0;
while (!fmr.eof()) while (!fmr.eof())
{ {
ticks += fmr.readNextMatchingOpcode(F_OP_PULSE); ticks += fmr.findEvent(F_BIT_PULSE);
nanoseconds_t transition = ticks*NS_PER_TICK; nanoseconds_t transition = ticks*NS_PER_TICK;
nanoseconds_t next; nanoseconds_t next;

View File

@@ -3,7 +3,6 @@
#include "reader.h" #include "reader.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"

View File

@@ -3,7 +3,6 @@
#include "reader.h" #include "reader.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"

View File

@@ -6,7 +6,6 @@
#include "amiga/amiga.h" #include "amiga/amiga.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "image.h"
#include "record.h" #include "record.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <fstream> #include <fstream>

View File

@@ -3,7 +3,6 @@
#include "reader.h" #include "reader.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"

View File

@@ -6,7 +6,6 @@
#include "apple2/apple2.h" #include "apple2/apple2.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "image.h"
#include "record.h" #include "record.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <fstream> #include <fstream>

View File

@@ -7,7 +7,6 @@
#include "brother/brother.h" #include "brother/brother.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "image.h"
#include "record.h" #include "record.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <fstream> #include <fstream>

View File

@@ -6,7 +6,6 @@
#include "c64/c64.h" #include "c64/c64.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "image.h"
#include "record.h" #include "record.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <fstream> #include <fstream>

View File

@@ -3,7 +3,6 @@
#include "reader.h" #include "reader.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"

View File

@@ -6,7 +6,6 @@
#include "f85/f85.h" #include "f85/f85.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "image.h"
#include "record.h" #include "record.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <fstream> #include <fstream>

View File

@@ -3,7 +3,6 @@
#include "reader.h" #include "reader.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"

View File

@@ -3,32 +3,37 @@
#include "reader.h" #include "reader.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"
#include "dataspec.h"
#include "ibm/ibm.h" #include "ibm/ibm.h"
#include "fmt/format.h" #include "fmt/format.h"
static FlagGroup flags { &readerFlags }; static FlagGroup flags { &readerFlags };
static IntFlag sectorIdBase( static IntFlag sectorIdBase(
{ "--sector-id-base" }, { "--ibm-sector-id-base" },
"Sector ID of the first sector.", "Sector ID of the first sector.",
1); 1);
static BoolFlag ignoreSideByte( static BoolFlag ignoreSideByte(
{ "--ignore-side-byte" }, { "--ibm-ignore-side-byte" },
"Ignore the side byte in the sector ID, and use the physical side instead.", "Ignore the side byte in the sector ID, and use the physical side instead.",
false); false);
static RangeFlag requiredSectors(
{ "--ibm-required-sectors" },
"A comma seperated list or range of sectors which must be on each track.",
"");
int mainReadIBM(int argc, const char* argv[]) int mainReadIBM(int argc, const char* argv[])
{ {
setReaderDefaultSource(":t=0-79:s=0-1"); setReaderDefaultSource(":t=0-79:s=0-1");
setReaderDefaultOutput("ibm.img"); setReaderDefaultOutput("ibm.img");
flags.parseFlags(argc, argv); flags.parseFlags(argc, argv);
IbmDecoder decoder(sectorIdBase, ignoreSideByte); IbmDecoder decoder(sectorIdBase, ignoreSideByte, requiredSectors);
readDiskCommand(decoder); readDiskCommand(decoder);
return 0; return 0;
} }

View File

@@ -6,7 +6,6 @@
#include "macintosh/macintosh.h" #include "macintosh/macintosh.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "image.h"
#include "record.h" #include "record.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <fstream> #include <fstream>

View File

@@ -4,7 +4,6 @@
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "mx/mx.h" #include "mx/mx.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"

View File

@@ -6,7 +6,6 @@
#include "victor9k/victor9k.h" #include "victor9k/victor9k.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "image.h"
#include "record.h" #include "record.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <fstream> #include <fstream>

View File

@@ -3,7 +3,6 @@
#include "reader.h" #include "reader.h"
#include "fluxmap.h" #include "fluxmap.h"
#include "decoders/decoders.h" #include "decoders/decoders.h"
#include "image.h"
#include "sector.h" #include "sector.h"
#include "sectorset.h" #include "sectorset.h"
#include "record.h" #include "record.h"

View File

@@ -1,10 +1,12 @@
#include "globals.h" #include "globals.h"
#include "flags.h" #include "flags.h"
#include "usb.h" #include "usb/usb.h"
#include "dataspec.h" #include "dataspec.h"
#include "protocol.h" #include "protocol.h"
static FlagGroup flags; static FlagGroup flags = {
&usbFlags,
};
static DataSpecFlag source( static DataSpecFlag source(
{ "--source", "-s" }, { "--source", "-s" },

View File

@@ -16,7 +16,7 @@ static int endSide;
static void syntax() static void syntax()
{ {
std::cout << "Syntax: fluxengine convert cwftoflux <cwffile> <fluxfile>\n"; std::cout << "Syntax: fluxengine convert scptoflux <scpfile> <fluxfile>\n";
exit(0); exit(0);
} }
@@ -28,15 +28,11 @@ static void check_for_error()
static int trackno(int strack) static int trackno(int strack)
{ {
if (startSide == endSide)
return strack;
return strack >> 1; return strack >> 1;
} }
static int headno(int strack) static int headno(int strack)
{ {
if (startSide == endSide)
return startSide;
return strack & 1; return strack & 1;
} }
@@ -65,6 +61,8 @@ static void read_header()
static void read_track(int strack) static void read_track(int strack)
{ {
uint32_t offset = Bytes(header.track[strack], 4).reader().read_le32(); uint32_t offset = Bytes(header.track[strack], 4).reader().read_le32();
if (offset == 0)
return;
ScpTrack trackheader; ScpTrack trackheader;
inputFile.seekg(offset, std::ios::beg); inputFile.seekg(offset, std::ios::beg);
@@ -112,7 +110,7 @@ static void read_track(int strack)
inputBytes += datalength*2; inputBytes += datalength*2;
} }
std::cout << fmt::format(" {} ms in {} input bytes and {} output bytes\n", std::cout << fmt::format(" {:.3f} ms in {} input bytes and {} output bytes\n",
fluxmap.duration() / 1e6, inputBytes, fluxmap.bytes()); fluxmap.duration() / 1e6, inputBytes, fluxmap.bytes());
sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap); sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap);
} }

View File

@@ -1,9 +1,11 @@
#include "globals.h" #include "globals.h"
#include "flags.h" #include "flags.h"
#include "usb.h" #include "usb/usb.h"
#include "protocol.h" #include "protocol.h"
static FlagGroup flags; static FlagGroup flags = {
&usbFlags,
};
static IntFlag drive( static IntFlag drive(
{ "--drive", "-d" }, { "--drive", "-d" },

View File

@@ -1,8 +1,10 @@
#include "globals.h" #include "globals.h"
#include "flags.h" #include "flags.h"
#include "usb.h" #include "usb/usb.h"
static FlagGroup flags; static FlagGroup flags = {
&usbFlags,
};
int mainTestBandwidth(int argc, const char* argv[]) int mainTestBandwidth(int argc, const char* argv[])
{ {

View File

@@ -1,10 +1,12 @@
#include "globals.h" #include "globals.h"
#include "flags.h" #include "flags.h"
#include "usb.h" #include "usb/usb.h"
#include "protocol.h" #include "protocol.h"
#include <fmt/format.h> #include <fmt/format.h>
static FlagGroup flags; static FlagGroup flags = {
&usbFlags,
};
static std::string display_voltages(struct voltages& v) static std::string display_voltages(struct voltages& v)
{ {

View File

@@ -6,7 +6,7 @@
static sqlite3* db; static sqlite3* db;
static void update_version_1_to_2() static void update_version_1_to_3()
{ {
for (const auto i : sqlFindFlux(db)) for (const auto i : sqlFindFlux(db))
{ {
@@ -33,6 +33,44 @@ static void update_version_1_to_2()
std::cout << std::endl; std::cout << std::endl;
} }
static void update_version_2_to_3()
{
for (const auto i : sqlFindFlux(db))
{
Fluxmap after;
const auto before = sqlReadFlux(db, i.first, i.second);
/* Remember, before does not contain valid opcodes! */
unsigned pending = 0;
for (uint8_t b : before->rawBytes())
{
switch (b)
{
case 0x80: /* pulse */
after.appendInterval(pending);
after.appendPulse();
pending = 0;
break;
case 0x81: /* index */
after.appendInterval(pending);
after.appendIndex();
pending = 0;
break;
default:
pending += b;
break;
}
}
after.appendInterval(pending);
sqlWriteFlux(db, i.first, i.second, after);
std::cout << '.' << std::flush;
}
std::cout << std::endl;
}
int mainUpgradeFluxFile(int argc, const char* argv[]) int mainUpgradeFluxFile(int argc, const char* argv[])
{ {
if (argc != 2) if (argc != 2)
@@ -71,14 +109,24 @@ int mainUpgradeFluxFile(int argc, const char* argv[])
if (version == FLUX_VERSION_1) if (version == FLUX_VERSION_1)
{ {
std::cout << "Upgrading to version 2\n"; std::cout << "Upgrading to version 3\n";
sqlStmt(db, "BEGIN;"); sqlStmt(db, "BEGIN;");
update_version_1_to_2(); update_version_1_to_3();
version = FLUX_VERSION_2; version = FLUX_VERSION_3;
sqlWriteIntProperty(db, "version", version); sqlWriteIntProperty(db, "version", version);
sqlStmt(db, "COMMIT;"); sqlStmt(db, "COMMIT;");
} }
if (version == FLUX_VERSION_2)
{
std::cout << "Upgrading to version 3\n";
sqlStmt(db, "BEGIN;");
update_version_2_to_3();
version = FLUX_VERSION_3;
sqlWriteIntProperty(db, "version", version);
sqlStmt(db, "COMMIT;");
}
std::cout << "Vacuuming\n"; std::cout << "Vacuuming\n";
sqlStmt(db, "VACUUM;"); sqlStmt(db, "VACUUM;");
std::cout << "Upgrade done\n"; std::cout << "Upgrade done\n";

View File

@@ -4,7 +4,6 @@
#include "amiga/amiga.h" #include "amiga/amiga.h"
#include "writer.h" #include "writer.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "image.h"
#include <fstream> #include <fstream>
static FlagGroup flags { &writerFlags, &amigaEncoderFlags }; static FlagGroup flags { &writerFlags, &amigaEncoderFlags };

View File

@@ -5,18 +5,31 @@
#include "brother/brother.h" #include "brother/brother.h"
#include "writer.h" #include "writer.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "image.h"
#include <fstream> #include <fstream>
static FlagGroup flags { &writerFlags, &brotherEncoderFlags }; static FlagGroup flags { &writerFlags, &brotherEncoderFlags };
static int brotherFormat = 240;
static ActionFlag preset120(
{ "--brother-preset-120" },
"Write the Brother 120kB format instead of the 240kB one.",
[] {
setWriterDefaultInput(":c=39:h=1:s=12:b=256");
brotherFormat = 120;
});
static IntFlag bias(
{ "--brother-track-bias" },
"Shift the entire format this many tracks on the disk.",
0);
int mainWriteBrother(int argc, const char* argv[]) int mainWriteBrother(int argc, const char* argv[])
{ {
setWriterDefaultInput(":c=78:h=1:s=12:b=256"); setWriterDefaultInput(":c=78:h=1:s=12:b=256");
setWriterDefaultDest(":d=0:t=0-77:s=0"); setWriterDefaultDest(":d=0:t=0-77:s=0");
flags.parseFlags(argc, argv); flags.parseFlags(argc, argv);
BrotherEncoder encoder; BrotherEncoder encoder(brotherFormat, bias);
writeDiskCommand(encoder); writeDiskCommand(encoder);
return 0; return 0;

View File

@@ -5,7 +5,6 @@
#include "ibm/ibm.h" #include "ibm/ibm.h"
#include "writer.h" #include "writer.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "image.h"
#include <fstream> #include <fstream>
static FlagGroup flags { &writerFlags }; static FlagGroup flags { &writerFlags };

View File

@@ -1,43 +0,0 @@
#include "globals.h"
#include "bytes.h"
#include "common/crunch.h"
static uint8_t outputbuffer[64];
static void test_crunch()
{
crunch_state_t cs = {};
Bytes inputdata = { 0x01, 0x7f, 0x80, 0x05, 0x80, 0x81, 0x01 };
cs.inputptr = inputdata.begin();
cs.inputlen = inputdata.size();
cs.outputptr = outputbuffer;
cs.outputlen = 64;
crunch(&cs);
donecrunch(&cs);
Bytes outputdata(outputbuffer, cs.outputptr - outputbuffer);
assert((outputdata == Bytes{ 0x01, 0x7f, 0xc5, 0x94, 0x04 }));
}
static void test_uncrunch()
{
crunch_state_t cs = {};
Bytes inputdata = { 0x01, 0x7f, 0xc5, 0x94, 0x04 };
cs.inputptr = inputdata.begin();
cs.inputlen = inputdata.size();
cs.outputptr = outputbuffer;
cs.outputlen = 64;
uncrunch(&cs);
doneuncrunch(&cs);
Bytes outputdata(outputbuffer, cs.outputptr - outputbuffer);
assert((outputdata == Bytes { 0x01, 0x7f, 0x80, 0x05, 0x80, 0x81, 0x01 }));
}
int main(int argc, const char* argv[])
{
test_crunch();
test_uncrunch();
return 0;
}

View File

@@ -13,6 +13,28 @@ static void test_split(void)
== std::vector<std::string>{"", "2", ""})); == std::vector<std::string>{"", "2", ""}));
assert((DataSpec::split("2", ",") assert((DataSpec::split("2", ",")
== std::vector<std::string>{"2"})); == std::vector<std::string>{"2"}));
assert((DataSpec::split("", ",")
== std::vector<std::string>()));
}
static void test_parserange(void)
{
assert(DataSpec::parseRange("")
== std::set<unsigned>());
assert(DataSpec::parseRange("1")
== std::set<unsigned>({1}));
assert(DataSpec::parseRange("1,3,5")
== std::set<unsigned>({1, 3, 5}));
assert(DataSpec::parseRange("1,1,1")
== std::set<unsigned>({1}));
assert(DataSpec::parseRange("2-3")
== std::set<unsigned>({2, 3}));
assert(DataSpec::parseRange("2+3")
== std::set<unsigned>({2, 3, 4}));
assert(DataSpec::parseRange("2+3x2")
== std::set<unsigned>({2, 4}));
assert(DataSpec::parseRange("9,2+3x2")
== std::set<unsigned>({2, 4, 9}));
} }
static void test_parsemod(void) static void test_parsemod(void)
@@ -104,6 +126,7 @@ static void test_imagespec(void)
int main(int argc, const char* argv[]) int main(int argc, const char* argv[])
{ {
test_split(); test_split();
test_parserange();
test_parsemod(); test_parsemod();
test_fluxspec(); test_fluxspec();
test_imagespec(); test_imagespec();

View File

@@ -49,43 +49,43 @@ static void test_stream_reader()
/* Simple one-byte intervals */ /* Simple one-byte intervals */
test_convert( test_convert(
Bytes{ 0x20, 0x20, 0x20, 0x20 }, Bytes{ 0x20, 0x20, 0x20, 0x20 },
Bytes{ 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80 } Bytes{ 0x8f, 0x8f, 0x8f, 0x8f }
); );
/* One-and-a-half-byte intervals */ /* One-and-a-half-byte intervals */
test_convert( test_convert(
Bytes{ 0x20, 0x00, 0x10, 0x20, 0x01, 0x10, 0x20 }, Bytes{ 0x20, 0x00, 0x10, 0x20, 0x01, 0x10, 0x20 },
Bytes{ 0x0f, 0x80, 0x07, 0x80, 0x0f, 0x80, 0x7f, 0x08, 0x80, 0x0f, 0x80 } Bytes{ 0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f }
); );
/* Two-byte intervals */ /* Two-byte intervals */
test_convert( test_convert(
Bytes{ 0x20, 0x0c, 0x00, 0x10, 0x20, 0x0c, 0x01, 0x10, 0x20 }, Bytes{ 0x20, 0x0c, 0x00, 0x10, 0x20, 0x0c, 0x01, 0x10, 0x20 },
Bytes{ 0x0f, 0x80, 0x07, 0x80, 0x0f, 0x80, 0x7f, 0x08, 0x80, 0x0f, 0x80 } Bytes{ 0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f }
); );
/* Overflow */ /* Overflow */
test_convert( test_convert(
Bytes{ 0x20, 0x0b, 0x10, 0x20 }, Bytes{ 0x20, 0x0b, 0x10, 0x20 },
Bytes{ 0x0f, 0x80 } + (Bytes{ 0x7f } * 0x101) + Bytes{ 0x63, 0x80, 0x0f, 0x80 } Bytes{ 0x8f } + (Bytes{ 0x3f } * 0x207) + Bytes{ 0xa9, 0x8f }
); );
/* Single-byte nop */ /* Single-byte nop */
test_convert( test_convert(
Bytes{ 0x20, 0x08, 0x20 }, Bytes{ 0x20, 0x08, 0x20 },
Bytes{ 0x0f, 0x80, 0x0f, 0x80 } Bytes{ 0x8f, 0x8f }
); );
/* Double-byte nop */ /* Double-byte nop */
test_convert( test_convert(
Bytes{ 0x20, 0x09, 0xde, 0x20 }, Bytes{ 0x20, 0x09, 0xde, 0x20 },
Bytes{ 0x0f, 0x80, 0x0f, 0x80 } Bytes{ 0x8f, 0x8f }
); );
/* Triple-byte nop */ /* Triple-byte nop */
test_convert( test_convert(
Bytes{ 0x20, 0x0a, 0xde, 0xad, 0x20 }, Bytes{ 0x20, 0x0a, 0xde, 0xad, 0x20 },
Bytes{ 0x0f, 0x80, 0x0f, 0x80 } Bytes{ 0x8f, 0x8f }
); );
/* OOB block */ /* OOB block */
@@ -98,7 +98,7 @@ static void test_stream_reader()
0x55, /* payload */ 0x55, /* payload */
0x20 /* data continues */ 0x20 /* data continues */
}, },
Bytes{ 0x0f, 0x80, 0x0f, 0x80 } Bytes{ 0x8f, 0x8f }
); );
} }