Compare commits

...

76 Commits

Author SHA1 Message Date
David Given
ce4cabde96 Rework the kmedian code to do all arithmetic in ticks rather than us. This
apparently improves things --- precision related, maybe?
2020-07-04 14:35:04 +01:00
David Given
ffbb6609c8 Merge from trunk. 2020-07-04 12:17:45 +01: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
5b66f803f3 First stage of the massive refactor to rework things to use the k-median
algorithm.
2020-06-27 20:17:33 +02:00
David Given
33d14a5fbe Bash the decoders into compiling-but-not-yet-working with the kmedian
classifier.
2020-06-27 14:46:35 +01:00
David Given
5d27b6d9aa Use the k-median code in the inspector for actual reals. 2020-06-27 11:55:53 +02:00
David Given
0d90dcee0f Replace the kmedian code with the weighted version --- vastly faster (a factor
of ten).
2020-06-27 11:25:03 +02:00
David Given
c89f5b9649 Add a test case for the kmedian code. 2020-06-26 20:53:55 +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
c5bc9a8cee Merge from trunk. 2020-06-26 16:00:43 +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
347e4d59a3 Fix the incredibly stupid bug when determining the median. 2020-06-26 15:24:04 +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
6e56cc3b0a First attempt at the optimised k-median code from kristomu@github. 2020-06-26 00:15:32 +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
108 changed files with 1962 additions and 1500 deletions

View File

@@ -10,7 +10,7 @@ jobs:
with:
fetch-depth: 1
- 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
run: make

View File

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

View File

@@ -848,6 +848,7 @@
<Data key="efd5f185-0c32-4824-ba72-3ceb5356f5a7" value="Clock_1" />
</Group>
<Group key="Pin">
<Data key="3e1862bb-be82-47b0-9549-7ebfe76b6f7b" value="Pin_2" />
<Data key="4a398466-709f-4228-9500-96178658e13e" value="RDATA" />
<Data key="5a3407c1-b434-4438-a7b4-b9dfd2280495" value="MOTEA" />
<Data key="8d318d8b-cf7b-4b6b-b02c-ab1c5c49d0ba" value="SW1" />
@@ -858,6 +859,7 @@
<Data key="472f8fdb-f772-44fb-8897-cc690694237b" value="WDATA" />
<Data key="736cb12b-c863-43d4-a8f0-42f06023f8b5" value="SIDE1" />
<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="a5d987c6-e45b-45b9-ad93-656fab06d720" value="TRK00" />
<Data key="a93ef5b3-00f4-42c0-8fad-0e275a7e2537" value="MOTEB" />
@@ -3963,6 +3965,11 @@
</Group>
</Group>
<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="0">
<Data key="Port Format" value="1,5" />
@@ -4064,6 +4071,11 @@
<Data key="Port Format" value="1,0" />
</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="0">
<Data key="Port Format" value="12,6" />

View File

@@ -39,20 +39,6 @@
<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="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>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
@@ -2795,6 +2781,72 @@
</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_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>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
@@ -3380,6 +3432,6 @@
</ignored_deps>
</CyGuid_495451fe-d201-4d01-b22d-5d3f5609ac37>
<boot_component v="" />
<current_generation v="138" />
<current_generation v="150" />
</CyGuid_fec8f9e8-2365-4bdb-96d3-a4380222e01b>
</CyXmlSerializer>

View File

@@ -1,7 +1,6 @@
//`#start header` -- edit after this line, do not edit this line
`include "cypress.v"
`include "../SuperCounter/SuperCounter.v"
//`#end` -- edit above this line, do not edit this line
// Generated on 12/11/2019 at 21:18
@@ -9,7 +8,7 @@
module Sampler (
output [2:0] debug_state,
output reg [7:0] opcode,
output req,
output reg req,
input clock,
input index,
input rdata,
@@ -19,90 +18,63 @@ module Sampler (
//`#start body` -- edit after this line, do not edit this line
localparam STATE_RESET = 0;
localparam STATE_WAITING = 1;
localparam STATE_OPCODE = 2;
// NOTE: Reset pulse is used in both clock domains, and must be long enough
// to be detected in both.
reg [1:0] state;
reg [6:0] counter;
reg [5:0] counter;
reg oldsampleclock;
wire sampleclocked;
assign sampleclocked = !oldsampleclock && sampleclock;
reg index_q;
reg rdata_q;
reg oldindex;
wire indexed;
assign indexed = !oldindex && index;
reg index_edge;
reg rdata_edge;
wire rdataed;
reg oldrdata;
assign rdataed = !oldrdata && rdata;
reg req_toggle;
assign req = (state == STATE_OPCODE);
always @(posedge clock)
always @(posedge sampleclock)
begin
if (reset)
begin
state <= STATE_RESET;
opcode <= 0;
oldsampleclock <= 0;
oldindex <= 0;
oldrdata <= 0;
index_edge <= 0;
rdata_edge <= 0;
index_q <= 0;
rdata_q <= 0;
counter <= 0;
req_toggle <= 0;
end
else
case (state)
STATE_RESET:
state <= STATE_WAITING;
STATE_WAITING:
begin
/* If something has happened, emit any necessary interval byte. */
if ((rdataed || indexed) && (counter != 0))
begin
opcode <= {0, counter};
state <= STATE_OPCODE;
end
else if (indexed)
begin
oldindex <= 1;
opcode <= 8'h81;
state <= STATE_OPCODE;
end
else if (rdataed)
begin
oldrdata <= 1;
opcode <= 8'h80;
state <= STATE_OPCODE;
end
else if (sampleclocked)
begin
oldsampleclock <= 1;
if (counter == 7'h7f)
begin
opcode <= {0, counter};
state <= STATE_OPCODE;
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
begin
/* Both index and rdata are active high -- positive-going edges
* indicate the start of an index pulse and read pulse, respectively.
*/
index_edge <= index && !index_q;
index_q <= index;
rdata_edge <= rdata && !rdata_q;
rdata_q <= rdata;
if (rdata_edge || index_edge || (counter == 6'h3f)) begin
opcode <= { rdata_edge, index_edge, counter };
req_toggle <= ~req_toggle;
counter <= 1; /* remember to count this tick */
end else begin
counter <= counter + 1;
end
end
end
reg req_toggle_q;
always @(posedge clock)
begin
if (reset) begin
req_toggle_q <= 0;
req <= 0;
end else begin
req_toggle_q <= req_toggle;
req <= (req_toggle != req_toggle_q);
end
end
//`#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
localparam STATE_LOAD = 0;
localparam STATE_WAITING = 1;
localparam STATE_PULSING = 2;
localparam STATE_INDEXING = 3;
localparam STATE_WRITING = 1;
localparam OPCODE_PULSE = 8'h80;
localparam OPCODE_INDEX = 8'h81;
reg [1:0] state;
reg [6:0] countdown;
reg state;
reg [5:0] countdown;
reg pulsepending;
assign req = (!reset && (state == STATE_LOAD));
assign wdata = (state == STATE_PULSING);
assign debug_state = state;
assign wdata = (!reset && (state == STATE_WRITING) && (countdown == 0) && pulsepending);
assign debug_state = 0;
reg olddataclock;
wire dataclocked;
@@ -52,49 +48,39 @@ begin
begin
state <= STATE_LOAD;
countdown <= 0;
pulsepending <= 0;
oldsampleclock <= 0;
end
else
begin
if (!oldsampleclock && sampleclock)
sampleclocked <= 1;
oldsampleclock <= sampleclock;
case (state)
STATE_LOAD:
/* Wait for a posedge on dataclocked, indicating an opcode has
begin
/* A posedge on dataclocked indicates that another opcode has
* arrived. */
if (dataclocked)
case (opcode)
OPCODE_PULSE:
state <= STATE_PULSING;
OPCODE_INDEX:
state <= STATE_INDEXING;
default:
begin
countdown <= opcode[6:0];
state <= STATE_WAITING;
end
endcase
begin
pulsepending <= opcode[7];
countdown <= opcode[5:0] - 1; /* compensate for delay in last tick */
state <= STATE_WRITING;
end
end
STATE_WAITING:
STATE_WRITING:
begin
if (sampleclocked)
begin
sampleclocked <= 0;
countdown <= countdown - 1;
/* Nasty fudge factor here to account for one to two
* sample ticks lost per pulse. */
if (countdown <= 2)
if (countdown == 0)
state <= STATE_LOAD;
else
countdown <= countdown - 1;
sampleclocked <= 0;
end
STATE_PULSING:
state <= STATE_LOAD;
STATE_INDEXING:
if (indexed)
state <= STATE_LOAD;
end
endcase
end
end

View File

Binary file not shown.

View File

@@ -5,7 +5,6 @@
#include <setjmp.h>
#include "project.h"
#include "../protocol.h"
#include "../lib/common/crunch.h"
#define MOTOR_ON_TIME 5000 /* milliseconds */
#define STEP_INTERVAL_TIME 6 /* ms */
@@ -17,6 +16,9 @@
#define STEP_TOWARDS0 1
#define STEP_AWAYFROM0 0
static bool drive0_present;
static bool drive1_present;
static volatile uint32_t clock = 0; /* ms */
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 dma_buffer[BUFFER_COUNT][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;
#define NEXT_BUFFER(b) (((b)+1) % BUFFER_COUNT)
@@ -42,6 +43,8 @@ static volatile bool dma_underrun = false;
#define DECLARE_REPLY_FRAME(STRUCT, TYPE) \
STRUCT r = {.f = { .type = TYPE, .size = sizeof(STRUCT) }}
static void stop_motor(void);
static void system_timer_cb(void)
{
CyGlobalIntDisable;
@@ -104,7 +107,10 @@ static void print(const char* msg, ...)
static void set_drive_flags(struct set_drive_frame* flags)
{
if (current_drive_flags.drive != flags->drive)
{
stop_motor();
homed = false;
}
current_drive_flags = *flags;
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)
{
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 */
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);
USBFS_ReadOutEP(ep, buffer, length);
while (USBFS_GetEPState(ep) == USBFS_OUT_BUFFER_FULL)
while (USBFS_GetEPState(ep) != USBFS_OUT_BUFFER_EMPTY)
;
return length;
}
@@ -184,19 +202,19 @@ static void step(int dir)
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++)
{
/* Don't keep stepping forever, because if a drive's
* not connected bad things happen. */
if (TRACK0_REG_Read())
break;
return true;
step(STEP_TOWARDS0);
}
/* Step to -1, which should be a nop, to reset the disk on disk change. */
step(STEP_TOWARDS0);
return false;
}
static void seek_to(int track)
@@ -311,13 +329,7 @@ static void cmd_bulk_read_test(struct any_frame* f)
CyWdtClear();
for (int y=0; y<256; y++)
{
USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
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)
;
usb_read(FLUXENGINE_DATA_OUT_EP_NUM, buffer);
for (unsigned z=0; z<sizeof(buffer); z++)
{
if (buffer[z] != (uint8)(x+y+z))
@@ -348,7 +360,7 @@ static void deinit_dma(void)
static void init_capture_dma(void)
{
dma_channel = SAMPLER_DMA_DmaInitialize(
2 /* bytes */,
1 /* bytes */,
true /* request per burst */,
HI16(CYDEV_PERIPH_BASE),
HI16(CYDEV_SRAM_BASE));
@@ -395,10 +407,6 @@ static void cmd_read(struct read_frame* f)
index_irq = false;
}
crunch_state_t cs = {};
cs.outputptr = xfer_buffer;
cs.outputlen = BUFFER_SIZE;
dma_writing_to_td = 0;
dma_reading_from_td = -1;
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. */
uint8_t dma_buffer_usage = 0;
while (dma_buffer_usage < BUFFER_SIZE)
{
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;
}
}
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, dma_buffer[dma_reading_from_td], BUFFER_SIZE);
count++;
dma_reading_from_td = NEXT_BUFFER(dma_reading_from_td);
}
}
abort:;
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);
/* If there's a complete packet waiting, send it. */
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);
}
USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, NULL, 0);
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
deinit_dma();
@@ -562,37 +537,18 @@ static void cmd_write(struct write_frame* f)
init_replay_dma();
bool writing = false; /* to the disk */
bool finished = false;
int packets = f->bytes_to_write / FRAME_SIZE;
bool finished = (packets == 0);
int count_written = 0;
int count_read = 0;
int packetwaiting = 0;
dma_writing_to_td = 0;
dma_reading_from_td = -1;
dma_underrun = false;
crunch_state_t cs = {};
cs.outputlen = BUFFER_SIZE;
USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
int old_reading_from_td = -1;
for (;;)
{
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);
}
}
//CyWdtClear();
/* 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))
goto abort;
/* Read crunched data, if necessary. */
if (cs.inputlen == 0)
uint8_t* buffer = dma_buffer[dma_writing_to_td];
if (finished)
{
if (finished)
{
/* There's no more data to read, so fake some. */
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);
}
}
}
/* There's no more data to read, so fake some. */
memset(buffer, 0x3f, BUFFER_SIZE);
}
/* If there *is* data waiting in the buffer, uncrunch it. */
if (cs.inputlen != 0)
else
{
cs.outputptr = dma_buffer[dma_writing_to_td] + BUFFER_SIZE - cs.outputlen;
uncrunch(&cs);
if (cs.outputlen == 0)
{
/* Completed a DMA buffer; queue it for writing. */
dma_writing_to_td = NEXT_BUFFER(dma_writing_to_td);
cs.outputlen = BUFFER_SIZE;
}
(void) usb_read(FLUXENGINE_DATA_OUT_EP_NUM, buffer);
count_read++;
if (count_read == packets)
finished = true;
}
dma_writing_to_td = NEXT_BUFFER(dma_writing_to_td);
/* Once all the buffers are full, start writing. */
@@ -693,7 +612,6 @@ static void cmd_write(struct write_frame* f)
}
}
abort:
print("done %d %d", dma_reading_from_td, dma_writing_to_td);
SEQUENCER_DMA_FINISHED_IRQ_Disable();
SEQUENCER_CONTROL_Write(1); /* reset */
@@ -713,27 +631,21 @@ abort:
* easier than trying to terminate the connection. */
while (count_read != packets)
{
if (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) == USBFS_OUT_BUFFER_FULL)
{
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++;
}
(void) usb_read(FLUXENGINE_DATA_OUT_EP_NUM, usb_buffer);
count_read++;
}
USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
}
deinit_dma();
print("write finished");
if (dma_underrun)
{
print("underrun!");
send_error(F_ERROR_UNDERRUN);
return;
}
print("success");
DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_WRITE_REPLY);
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)
{
if (drive0_present && !drive1_present)
f->drive = 0;
if (drive1_present && !drive0_present)
f->drive = 1;
set_drive_flags(f);
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)
{
CyGlobalIntEnable;
@@ -927,11 +858,11 @@ int main(void)
DRIVESELECT_REG_Write(0);
UART_Start();
USBFS_Start(0, USBFS_DWR_VDDD_OPERATION);
USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
detect_drives();
CyWdtStart(CYWDT_1024_TICKS, CYWDT_LPMODE_DISABLED);
/* UART_PutString("GO\r"); */
for (;;)
{
CyWdtClear();
@@ -947,7 +878,7 @@ int main(void)
{
print("Waiting for USB...");
while (!USBFS_GetConfiguration())
;
CyWdtClear();
print("USB ready");
USBFS_EnableOutEP(FLUXENGINE_CMD_OUT_EP_NUM);
}

View File

@@ -39,7 +39,7 @@ CFLAGS += -Ilib -Idep/fmt -Iarch
export OBJDIR = .obj
all: .obj/build.ninja
@ninja -f .obj/build.ninja -v
@ninja -f .obj/build.ninja
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>
</div>
**Important note.** On 2019-02-09 I did a hardware redesign and moved the pins on
the board. Sorry for the inconvenience, but it means you don't have to modify
the board any more to make it work. If you built the hardware prior to then,
you'll need to adjust it.
**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.
**Important note.** On 2020-04-02 I changed the bytecode format (and firmware).
Flux files will need to be upgraded with `fluxengine upgradefluxfile`. The new
format should be more reliable and use way, way less bandwidth. Sorry for the
inconvenience.
Where?
------

View File

@@ -13,6 +13,9 @@ class AesLanierDecoder : public AbstractDecoder
public:
virtual ~AesLanierDecoder() {}
int getDecoderBands() const { return 4; }
bool isInterleaved() const { return true; }
RecordType advanceToNextRecord();
void decodeSectorRecord();
};

View File

@@ -26,8 +26,8 @@ static Bytes reverse_bits(const Bytes& input)
AbstractDecoder::RecordType AesLanierDecoder::advanceToNextRecord()
{
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
if (_fmr->eof() || !_sector->clock)
nanoseconds_t clock = _fmr->seekToPattern(SECTOR_PATTERN);
if (_fmr->eof() || !clock)
return UNKNOWN_RECORD;
return SECTOR_RECORD;
}

View File

@@ -13,13 +13,15 @@ class Sector;
class Fluxmap;
class SectorSet;
class AmigaDecoder : public AbstractDecoder
class AmigaDecoder : public AbstractMfmDecoder
{
public:
virtual ~AmigaDecoder() {}
RecordType advanceToNextRecord();
void decodeSectorRecord();
std::set<unsigned> requiredSectors(Track& track) const;
};
class AmigaEncoder : public AbstractEncoder

View File

@@ -23,8 +23,8 @@ static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD);
AbstractDecoder::RecordType AmigaDecoder::advanceToNextRecord()
{
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
if (_fmr->eof() || !_sector->clock)
_fmr->seekToPattern(SECTOR_PATTERN);
if (_fmr->eof())
return UNKNOWN_RECORD;
return SECTOR_RECORD;
}
@@ -32,6 +32,8 @@ AbstractDecoder::RecordType AmigaDecoder::advanceToNextRecord()
void AmigaDecoder::decodeSectorRecord()
{
const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16);
if (rawbits.size() < (AMIGA_RECORD_SIZE*16))
return;
const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE);
@@ -56,3 +58,10 @@ void AmigaDecoder::decodeSectorRecord()
_sector->data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
_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

@@ -10,7 +10,7 @@
class Sector;
class Fluxmap;
class Apple2Decoder : public AbstractDecoder
class Apple2Decoder : public AbstractGcrDecoder
{
public:
virtual ~Apple2Decoder() {}

View File

@@ -68,7 +68,7 @@ uint8_t combine(uint16_t word)
AbstractDecoder::RecordType Apple2Decoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
_fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)

View File

@@ -9,7 +9,8 @@
#define BROTHER_DATA_RECORD_CHECKSUM 3
#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
class Sector;
@@ -20,6 +21,9 @@ class BrotherDecoder : public AbstractDecoder
public:
virtual ~BrotherDecoder() {}
int getDecoderBands() const { return 2; }
bool isInterleaved() const { return false; }
RecordType advanceToNextRecord();
void decodeSectorRecord();
void decodeDataRecord();
@@ -28,8 +32,16 @@ public:
class BrotherEncoder : public AbstractEncoder
{
public:
BrotherEncoder(int format, int bias):
_format(format),
_bias(bias)
{}
virtual ~BrotherEncoder() {}
private:
int _format;
int _bias;
public:
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
};

View File

@@ -57,7 +57,7 @@ static int decode_header_gcr(uint16_t word)
AbstractDecoder::RecordType BrotherDecoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
_fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)

View File

@@ -129,9 +129,25 @@ static int charToInt(char c)
std::unique_ptr<Fluxmap> BrotherEncoder::encode(
int physicalTrack, int physicalSide, const SectorSet& allSectors)
{
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_DISK)
|| (physicalSide != 0))
int logicalTrack;
if (physicalSide != 0)
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;
const std::string& skew = sectorSkew.get();
@@ -146,10 +162,10 @@ std::unique_ptr<Fluxmap> BrotherEncoder::encode(
double dataMs = headerMs + postHeaderSpacingMs;
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 });
write_sector_header(bits, cursor, physicalTrack, sectorId);
write_sector_header(bits, cursor, logicalTrack, sectorId);
fillBitmapTo(bits, cursor, dataCursor, { true, false });
write_sector_data(bits, cursor, sectorData->data);
}

View File

@@ -8,7 +8,7 @@
class Sector;
class Fluxmap;
class Commodore64Decoder : public AbstractDecoder
class Commodore64Decoder : public AbstractGcrDecoder
{
public:
virtual ~Commodore64Decoder() {}

View File

@@ -55,7 +55,7 @@ static Bytes decode(const std::vector<bool>& bits)
AbstractDecoder::RecordType Commodore64Decoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
_fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)

View File

@@ -55,7 +55,7 @@ static Bytes decode(const std::vector<bool>& bits)
AbstractDecoder::RecordType DurangoF85Decoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
_fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)

View File

@@ -8,7 +8,7 @@
class Sector;
class Fluxmap;
class DurangoF85Decoder : public AbstractDecoder
class DurangoF85Decoder : public AbstractGcrDecoder
{
public:
virtual ~DurangoF85Decoder() {}

View File

@@ -102,7 +102,7 @@ static uint16_t checksum(const Bytes& bytes)
AbstractDecoder::RecordType Fb100Decoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(SECTOR_ID_PATTERN, matcher);
_fmr->seekToPattern(SECTOR_ID_PATTERN, matcher);
if (matcher == &SECTOR_ID_PATTERN)
return RecordType::SECTOR_RECORD;
return RecordType::UNKNOWN_RECORD;
@@ -132,4 +132,4 @@ void Fb100Decoder::decodeSectorRecord()
_sector->data.writer().append(id.slice(5, 12)).append(payload);
_sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
}
}

View File

@@ -9,7 +9,7 @@ class Sector;
class Fluxmap;
class Track;
class Fb100Decoder : public AbstractDecoder
class Fb100Decoder : public AbstractFmDecoder
{
public:
virtual ~Fb100Decoder() {}

View File

@@ -57,11 +57,11 @@ const FluxPattern FM_TRS80DAM1_PATTERN(16, 0xf56b);
/*
* TRS80DAM2 record:
* flux: XXXX-X-X-XX-XXX- = 0xf56c
* flux: XXXX-X-X-XX-XXX- = 0xf56e
* clock: X X - - - X X X = 0xc7
* 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:
* 0xA1 is:
@@ -92,7 +92,7 @@ const FluxMatchers ANY_RECORD_PATTERN(
AbstractDecoder::RecordType IbmDecoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
_fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
/* If this is the MFM prefix byte, the the decoder is going to expect three
* extra bytes on the front of the header. */

View File

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

View File

@@ -29,21 +29,27 @@ struct IbmIdam
uint8_t crc[2];
};
class IbmDecoder : public AbstractDecoder
class IbmDecoder : public AbstractMfmDecoder
{
public:
IbmDecoder(unsigned sectorBase, bool ignoreSideByte=false):
IbmDecoder(unsigned sectorBase, bool ignoreSideByte=false,
const std::set<unsigned> requiredSectors=std::set<unsigned>()):
_sectorBase(sectorBase),
_ignoreSideByte(ignoreSideByte)
_ignoreSideByte(ignoreSideByte),
_requiredSectors(requiredSectors)
{}
RecordType advanceToNextRecord();
void decodeSectorRecord();
void decodeDataRecord();
std::set<unsigned> requiredSectors(Track& track) const
{ return _requiredSectors; }
private:
unsigned _sectorBase;
bool _ignoreSideByte;
std::set<unsigned> _requiredSectors;
unsigned _currentSectorSize;
unsigned _currentHeaderLength;
};

View File

@@ -127,7 +127,7 @@ uint8_t decode_side(uint8_t side)
AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
_fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
@@ -184,3 +184,25 @@ void MacintoshDecoder::decodeDataRecord()
_sector->data.clear();
_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

@@ -10,7 +10,7 @@
class Sector;
class Fluxmap;
class MacintoshDecoder : public AbstractDecoder
class MacintoshDecoder : public AbstractGcrDecoder
{
public:
virtual ~MacintoshDecoder() {}
@@ -18,6 +18,8 @@ public:
RecordType advanceToNextRecord();
void decodeSectorRecord();
void decodeDataRecord();
std::set<unsigned> requiredSectors(Track& track) const;
};
#endif

View File

@@ -35,22 +35,15 @@ AbstractDecoder::RecordType MxDecoder::advanceToNextRecord()
{
/* First sector in the track: look for the sync marker. */
const FluxMatcher* matcher = nullptr;
_sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher);
_fmr->seekToPattern(ID_PATTERN, matcher);
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)
{
/* That was the last sector on the disk. */
return UNKNOWN_RECORD;
}
else
{
/* Otherwise we assume the clock from the first sector is still valid.
* The decoder framwork will automatically stop when we hit the end of
* the track. */
_sector->clock = _clock;
}
_currentSector++;
return SECTOR_RECORD;

View File

@@ -3,7 +3,7 @@
#include "decoders/decoders.h"
class MxDecoder : public AbstractDecoder
class MxDecoder : public AbstractFmDecoder
{
public:
virtual ~MxDecoder() {}

View File

@@ -57,7 +57,7 @@ static Bytes decode(const std::vector<bool>& bits)
AbstractDecoder::RecordType Victor9kDecoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
_fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)

View File

@@ -9,7 +9,7 @@
class Sector;
class Fluxmap;
class Victor9kDecoder : public AbstractDecoder
class Victor9kDecoder : public AbstractGcrDecoder
{
public:
virtual ~Victor9kDecoder() {}

View File

@@ -18,7 +18,7 @@ AbstractDecoder::RecordType ZilogMczDecoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_fmr->seekToIndexMark();
_sector->clock = _fmr->seekToPattern(SECTOR_START_PATTERN, matcher);
_fmr->seekToPattern(SECTOR_START_PATTERN, matcher);
if (matcher == &SECTOR_START_PATTERN)
return SECTOR_RECORD;
return UNKNOWN_RECORD;

View File

@@ -4,7 +4,7 @@
class Sector;
class Fluxmap;
class ZilogMczDecoder : public AbstractDecoder
class ZilogMczDecoder : public AbstractGcrDecoder
{
public:
virtual ~ZilogMczDecoder() {}

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)
(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
(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
@@ -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,
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
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
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
the port and proceed normally.
## Building the client
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.
- 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 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
anything.
@@ -197,11 +200,176 @@ dependencies and you should be able to put it anywhere.
If it doesn't build, please [get in
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
The board's now assembled and programmed. Plug it into your drive, strip the
plastic off the little USB connector and plug that into your computer, and
you're ready to start using it.
You should now be ready to go. You'll want to read [the client
documentation](using.md) for information about how to actually do interesting
things.
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

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
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
real track is, but normal PC drives aren't capable of doing this.
Particularly with the 120kB disks, you might want to fiddle with the start
track (e.g. `:t=0-79x2`) to get a clean read. Keep an eye on the bad sector
map that's dumped at the end of a read.
real track is, but normal PC drives aren't capable of doing this. Particularly
with the 120kB disks, you might want to fiddle with the start track (e.g.
`:t=0-79x2`) to get a clean read. Keep an eye on the bad sector map that's
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
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
investigate.
Reading discs
Reading disks
-------------
Just do:
@@ -41,7 +43,7 @@ fluxengine read brother
You should end up with a `brother.img` which is 239616 bytes long.
Writing discs
Writing disks
-------------
Just do:
@@ -53,6 +55,27 @@ fluxengine write brother
...and it'll write a `brother.img` file which is 239616 bytes long to the
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
----------------
@@ -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
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
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.
@@ -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
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,
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:
- `--sector-id-base`: specifies the ID of the first sector; this defaults
to 1. Some formats (like the Acorn ones) start at 0. This can't be
- `--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
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
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
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
two sides). This option tells FluxEngine to ignore the side byte completely
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
-------------

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
stream of pulses to the disk.
The bytecode format represents an interval between pulses as a byte, a pulse
as a byte, and the index hole as a byte. Timer overflows are handled by
sending multiple intervals in a row. However, the USB transport applies a
simple compression system to this in order to get the USB bandwidth down to
something manageable.
The bytecode format is very simple with a six-bit interval since the previous
event in the lower six bits and the top two bits are set of a pulse or an index
hole (or both, or neither).
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
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
100,000 pulses. Each one is encoded as two bytes, one for the interval and
one to generate the pulse; so that revolution generates 200kB of data.
(Extremely approximately. The actual figure is less.)
100,000 pulses. Each one is encoded as a single byte; so that revolution
generates 100kB of data. (Extremely approximately. The actual figure varies
depending on what data is stored on the disk.)
(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

View File

@@ -1,78 +1,8 @@
Using a FluxEngine
==================
So you've [built the hardware](building.md)! 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.
So you've [built the hardware](building.md), programmed and tested it! What
now?
## The programs
@@ -81,10 +11,33 @@ moving too quickly for the documentation to keep up. It does respond to
`--help` or `help` depending on context. There are some common properties,
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.
### Source and destination specifiers
When reading from or writing to _a disk_ (or a file pretending to be a disk),
use the `--source` (`-s`) and `--dest` (`-d`) options to tell FluxEngine
When reading from or writing _flux_ (either from or to a real disk, or a flux
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:
```
@@ -94,7 +47,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 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
`:t=0-3` and `:t=0,1,2,3` are equivalent.
@@ -120,18 +73,15 @@ If you _don't_ specify a modifier, you'll get the default, which should be
sensible for the command you're using.
**Important note:** FluxEngine _always_ uses zero-based units (even if the
*disk format says otherwise).
disk format says otherwise).
### Input and output specifiers
These use a very similar syntax to the source and destination specifiers
(because they're based on the same microformat library!) but are used for
input and output _images_: i.e. nicely lined up arrays of sectors which you
can actually do something with.
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:
When reading or writing _file system images_, use the `--input` (`-i`) and
`--output` (`-o`) options to specify the file and file format. These use a very
similar syntax to the source and destination specifiers (because they're based
on the same microformat library!) but with different specifiers. Also, the
exact format varies according to the extension:
- `.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
@@ -191,17 +141,23 @@ here.](http://www.retrotechnology.com/herbs_stuff/guzis.html)
These flags apply to many operations and are useful for modifying the overall
behaviour.
- `--revolutions=X`: when reading, spin the disk X times. Many formats
require `--revolutions=2` (which should happen automatically); or you can
increase the number to sample more data.
- `--revolutions=X`: when reading, spin the disk X times. X can be a floating
point number. The default is usually 1.25. Some formats default to 1.
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
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
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
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.
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
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
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.
### The commands
@@ -211,60 +167,62 @@ installed anywhere and after building you'll find them in the `.obj`
directory.
- `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.
Mainly useful for debugging.
Mainly useful for debugging.
- `fluxengine read *`: reads various formats of disk. See the per-format
documentation linked from the table above. These all take an optional
`--write-flux` option which will cause the raw flux to be written to the
specified file. There are various `--dump` options for showing raw data
during the decode process.
documentation linked from the table [in the index page](../README.md).
These all take an optional `--write-flux` option which will cause the raw
flux to be written to the specified file as well as the normal decode.
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
per-format documentation above.
per-format documentation [in the index page](../README.md).
- `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
another disk. (See the [FAQ](faq.md) for more information.) It's mainly
useful for flux files synthesised by the other `fluxengine write` commands.
than you might think: you can't reliably write flux files read from a disk
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.
- `fluxengine writetestpattern`: writes regular pulses (at a configurable
interval) to the disk. Useful for testing drive jitter, erasing disks in a
more secure fashion, or simply debugging. Goes well with `fluxengine
inspect`.
interval) to the disk. Useful for testing drive jitter, erasing disks in a
more secure fashion, or simply debugging. Goes well with `fluxengine
inspect`.
- `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
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
600kB/s for FluxEngine to work for DD disks, and 900 to 950kB/s for HD
disks. You don't need a disk in the drive for this one.
- `fluxengine test bandwidth`: measures your USB throughput. You don't need
a disk in the drive for this one.
- `fluxengine test voltages`: measures your FDD bus signal voltages, which
is useful for testing for termination issues.
- `fluxengine test voltages`: measures your FDD bus signal voltages, which is
useful for testing for termination issues.
- `fluxengine upgradefluxfile`: occasionally I need to upgrade the flux
file format in a non-backwards-compatible way; this tool will upgrade flux
files to the new format.
- `fluxengine upgradefluxfile`: occasionally I need to upgrade the flux file
format in a non-backwards-compatible way; this tool will upgrade flux files
to the new format.
- `fluxengine convert`: converts flux files from various formats to various
other formats. You can use this to convert Catweasel flux files to
FluxEngine's native format, FluxEngine flux files to various other formats
useful for debugging (including VCD which can be loaded into
[sigrok](http://sigrok.org)), and bidirectional conversion to and from
Supercard Pro `.scp` format.
other formats. You can use this to convert Catweasel flux files to
FluxEngine's native format, FluxEngine flux files to various other formats
useful for debugging (including VCD which can be loaded into
[sigrok](http://sigrok.org)), and bidirectional conversion to and from
Supercard Pro `.scp` format.
**Important SCP note:** import (`fluxengine convert scptoflux`) should be
fairly robust, but export (`fluxengine convert fluxtoscp`) should only 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.
As ever, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new) with any reports.
**Important SCP note:** import (`fluxengine convert scptoflux`) should be
fairly robust, but export (`fluxengine convert fluxtoscp`) should only 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.
As ever, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new) with any
reports.
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
@@ -290,8 +248,10 @@ disk). For a 5.25" disk, use `--visualiser-period=166`.
Supplied with FluxEngine, but not part of FluxEngine, are some little tools I
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
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 "bytes.h"
#include "fmt/format.h"
#include "common/crunch.h"
#include <fstream>
#include <zlib.h>
@@ -249,60 +248,6 @@ Bytes Bytes::decompress() const
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
{
std::ofstream f(filename, std::ios::out | std::ios::binary);
@@ -313,6 +258,11 @@ void Bytes::writeToFile(const std::string& filename) const
f.close();
}
void Bytes::writeTo(std::ostream& stream) const
{
stream.write((const char*) cbegin(), size());
}
ByteReader Bytes::reader() const
{
return ByteReader(*this);

View File

@@ -51,14 +51,13 @@ public:
Bytes swab() const;
Bytes compress() const;
Bytes decompress() const;
Bytes crunch() const;
Bytes uncrunch() const;
std::vector<bool> toBits() const;
ByteReader reader() const;
ByteWriter writer();
void writeToFile(const std::string& filename) const;
void writeTo(std::ostream& stream) const;
private:
std::shared_ptr<std::vector<uint8_t>> _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;
size_t start = 0;
size_t end = 0;
size_t len = 0;
do
{
end = s.find(delimiter,start);
len = end - start;
std::string token = s.substr(start, len);
ret.emplace_back( token );
start += len + delimiter.length();
}
while (end != std::string::npos);
if (!s.empty())
{
size_t start = 0;
size_t end = 0;
size_t len = 0;
do
{
end = s.find(delimiter,start);
len = end - start;
std::string token = s.substr(start, len);
ret.emplace_back( token );
start += len + delimiter.length();
}
while (end != std::string::npos);
}
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;
if (!std::regex_match(spec, match, MOD_REGEX))
Error() << "invalid data modifier syntax '" << spec << "'";
Modifier m;
m.name = match[1];
m.source = spec;
for (auto& data : split(match[2], ","))
std::set<unsigned> result;
for (auto& data : split(data, ","))
{
int start = 0;
int count = 1;
@@ -64,9 +61,24 @@ DataSpec::Modifier DataSpec::parseMod(const std::string& spec)
Error() << "mod '" << data << "' specifies an illegal quantity";
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;
}
@@ -74,7 +86,7 @@ void DataSpec::set(const std::string& spec)
{
std::vector<std::string> words = split(spec, ":");
if (words.size() == 0)
Error() << "empty data specification (you have to specify *something*)";
return;
filename = words[0];
if (words.size() > 1)

View File

@@ -34,6 +34,8 @@ public:
public:
static std::vector<std::string> split(
const std::string& s, const std::string& delimiter);
static std::set<unsigned> parseRange(const std::string& spec);
static Modifier parseMod(const std::string& spec);
public:
@@ -117,4 +119,34 @@ private:
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

View File

@@ -16,26 +16,27 @@ void AbstractDecoder::decodeToSectors(Track& track)
Sector sector;
sector.physicalSide = track.physicalSide;
sector.physicalTrack = track.physicalTrack;
FluxmapReader fmr(*track.fluxmap);
FluxmapReader fmr(*track.fluxmap, getDecoderBands(), isInterleaved());
_track = &track;
_sector = &sector;
_fmr = &fmr;
_track->intervals = fmr.intervals();
beginTrack();
for (;;)
{
Fluxmap::Position recordStart = fmr.tell();
sector.clock = 0;
sector.intervals = fmr.intervals();
sector.status = Sector::MISSING;
sector.data.clear();
sector.logicalSector = sector.logicalSide = sector.logicalTrack = 0;
RecordType r = advanceToNextRecord();
if (fmr.eof() || !sector.clock)
if (fmr.eof() || sector.intervals.empty())
return;
if ((r == UNKNOWN_RECORD) || (r == DATA_RECORD))
{
fmr.readNextMatchingOpcode(F_OP_PULSE);
fmr.findEvent(F_BIT_PULSE);
continue;
}
@@ -56,7 +57,7 @@ void AbstractDecoder::decodeToSectors(Track& track)
r = advanceToNextRecord();
if (r != UNKNOWN_RECORD)
break;
if (fmr.readNextMatchingOpcode(F_OP_PULSE) == 0)
if (fmr.findEvent(F_BIT_PULSE) == 0)
break;
}
recordStart = fmr.tell();
@@ -80,11 +81,18 @@ void AbstractDecoder::pushRecord(const Fluxmap::Position& start, const Fluxmap::
RawRecord record;
record.physicalSide = _track->physicalSide;
record.physicalTrack = _track->physicalTrack;
record.clock = _sector->clock;
record.intervals = _sector->intervals;
record.position = start;
_fmr->seek(start);
record.data = toBytes(_fmr->readRawBits(end, _sector->clock));
record.data = toBytes(_fmr->readRawBits(end));
_track->rawrecords.push_back(record);
_fmr->seek(here);
}
std::set<unsigned> AbstractDecoder::requiredSectors(Track& track) const
{
static std::set<unsigned> empty;
return empty;
}

View File

@@ -44,7 +44,7 @@ public:
void pushRecord(const Fluxmap::Position& start, const Fluxmap::Position& end);
std::vector<bool> readRawBits(unsigned count)
{ return _fmr->readRawBits(count, _sector->clock); }
{ return _fmr->readRawBits(count); }
Fluxmap::Position tell()
{ return _fmr->tell(); }
@@ -52,7 +52,15 @@ public:
void seek(const Fluxmap::Position& 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:
virtual int getDecoderBands() const = 0;
virtual bool isInterleaved() const = 0;
virtual void beginTrack() {};
virtual RecordType advanceToNextRecord() = 0;
virtual void decodeSectorRecord() = 0;
@@ -63,4 +71,25 @@ protected:
Sector* _sector;
};
class AbstractFmDecoder : public AbstractDecoder
{
protected:
int getDecoderBands() const { return 2; }
bool isInterleaved() const { return false; }
};
class AbstractMfmDecoder : public AbstractDecoder
{
protected:
int getDecoderBands() const { return 3; }
bool isInterleaved() const { return true; }
};
class AbstractGcrDecoder : public AbstractDecoder
{
protected:
int getDecoderBands() const { return 3; }
bool isInterleaved() const { return false; }
};
#endif

View File

@@ -3,6 +3,7 @@
#include "decoders/fluxmapreader.h"
#include "flags.h"
#include "protocol.h"
#include "kmedian.h"
#include "fmt/format.h"
#include <numeric>
#include <math.h>
@@ -30,16 +31,35 @@ static DoubleFlag minimumClockUs(
"Refuse to detect clocks shorter than this, to avoid false positives.",
0.75);
int FluxmapReader::readOpcode(unsigned& ticks)
FluxmapReader::FluxmapReader(const Fluxmap& fluxmap):
_fluxmap(fluxmap),
_bytes(fluxmap.ptr()),
_size(fluxmap.bytes()),
_isInterleaved(false)
{
rewind();
}
FluxmapReader::FluxmapReader(const Fluxmap& fluxmap, int bands, bool isInterleaved):
_fluxmap(fluxmap),
_bytes(fluxmap.ptr()),
_size(fluxmap.bytes()),
_isInterleaved(isInterleaved)
{
for (unsigned ticks : optimalKMedian(fluxmap, bands))
_intervals.push_back(ticks * NS_PER_TICK);
rewind();
}
uint8_t FluxmapReader::getNextEvent(unsigned& ticks)
{
ticks = 0;
while (!eof())
{
uint8_t b = _bytes[_pos.bytes++];
if (b < 0x80)
ticks += b;
else
ticks += b & 0x3f;
if (b & (F_BIT_PULSE|F_BIT_INDEX))
{
_pos.ticks += ticks;
return b;
@@ -47,33 +67,34 @@ int FluxmapReader::readOpcode(unsigned& ticks)
}
_pos.ticks += ticks;
return -1;
return 0;
}
unsigned FluxmapReader::readNextMatchingOpcode(uint8_t opcode)
unsigned FluxmapReader::findEvent(uint8_t target)
{
unsigned ticks = 0;
for (;;)
{
unsigned thisTicks;
int op = readOpcode(thisTicks);
uint8_t bits = getNextEvent(thisTicks);
ticks += thisTicks;
if (op == -1)
if (eof())
return 0;
if (op == opcode)
if (bits & target)
return ticks;
}
}
unsigned FluxmapReader::readInterval(nanoseconds_t clock)
unsigned FluxmapReader::readInterval()
{
nanoseconds_t clock = _intervals.front();
unsigned thresholdTicks = (clock * pulseDebounceThreshold) / NS_PER_TICK;
unsigned ticks = 0;
while (ticks < thresholdTicks)
{
unsigned thisTicks = readNextMatchingOpcode(F_OP_PULSE);
unsigned thisTicks = findEvent(F_BIT_PULSE);
if (!thisTicks)
break;
ticks += thisTicks;
@@ -95,7 +116,8 @@ static int findLowestSetBit(uint64_t value)
}
FluxPattern::FluxPattern(unsigned bits, uint64_t pattern):
_bits(bits)
_bits(bits),
_pattern(pattern)
{
const uint64_t TOPBIT = 1ULL << 63;
@@ -196,7 +218,7 @@ void FluxmapReader::seek(nanoseconds_t ns)
while (!eof() && (_pos.ticks < ticks))
{
unsigned t;
readOpcode(t);
getNextEvent(t);
}
_pos.zeroes = 0;
}
@@ -237,7 +259,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
positions[i] = positions[i+1];
candidates[i] = candidates[i+1];
}
candidates[intervalCount] = readNextMatchingOpcode(F_OP_PULSE);
candidates[intervalCount] = findEvent(F_BIT_PULSE);
positions[intervalCount] = tell();
}
@@ -248,13 +270,13 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
void FluxmapReader::seekToIndexMark()
{
readNextMatchingOpcode(F_OP_INDEX);
findEvent(F_BIT_INDEX);
_pos.zeroes = 0;
}
bool FluxmapReader::readRawBit(nanoseconds_t clockPeriod)
bool FluxmapReader::readRawBit()
{
assert(clockPeriod != 0);
assert(!_intervals.empty());
if (_pos.zeroes)
{
@@ -262,30 +284,38 @@ bool FluxmapReader::readRawBit(nanoseconds_t clockPeriod)
return false;
}
nanoseconds_t interval = readInterval(clockPeriod)*NS_PER_TICK;
double clocks = (double)interval / clockPeriod + clockIntervalBias;
nanoseconds_t interval = readInterval()*NS_PER_TICK;
int clocks = 0;
while (clocks < _intervals.size()-1)
{
float median = (_intervals[clocks] + _intervals[clocks+1])/2.0;
if (interval < median)
break;
clocks++;
}
if (clocks < 1.0)
clocks = 1.0;
_pos.zeroes = (int)round(clocks) - 1;
if (_isInterleaved)
clocks++;
_pos.zeroes = clocks;
return true;
}
std::vector<bool> FluxmapReader::readRawBits(unsigned count, nanoseconds_t clockPeriod)
std::vector<bool> FluxmapReader::readRawBits(unsigned count)
{
std::vector<bool> result;
while (!eof() && count--)
{
bool b = readRawBit(clockPeriod);
bool b = readRawBit();
result.push_back(b);
}
return result;
}
std::vector<bool> FluxmapReader::readRawBits(const Fluxmap::Position& until, nanoseconds_t clockPeriod)
std::vector<bool> FluxmapReader::readRawBits(const Fluxmap::Position& until)
{
std::vector<bool> result;
while (!eof() && (_pos.bytes < until.bytes))
result.push_back(readRawBit(clockPeriod));
result.push_back(readRawBit());
return result;
}

View File

@@ -41,6 +41,7 @@ private:
std::vector<unsigned> _intervals;
unsigned _length;
unsigned _bits;
uint64_t _pattern;
unsigned _highzeroes;
bool _lowzero = false;
@@ -67,14 +68,8 @@ private:
class FluxmapReader
{
public:
FluxmapReader(const Fluxmap& fluxmap):
_fluxmap(fluxmap),
_bytes(fluxmap.ptr()),
_size(fluxmap.bytes())
{
rewind();
}
FluxmapReader(const Fluxmap& fluxmap);
FluxmapReader(const Fluxmap& fluxmap, int bands, bool isInterleaved);
FluxmapReader(const Fluxmap&& fluxmap) = delete;
void rewind()
@@ -96,9 +91,9 @@ public:
_pos = pos;
}
int readOpcode(unsigned& ticks);
unsigned readNextMatchingOpcode(uint8_t opcode);
unsigned readInterval(nanoseconds_t clock); /* with debounce support */
uint8_t getNextEvent(unsigned& ticks);
unsigned findEvent(uint8_t bits);
unsigned readInterval(); /* with debounce support */
/* Important! You can only reliably seek to 1 bits. */
void seek(nanoseconds_t ns);
@@ -107,15 +102,19 @@ public:
nanoseconds_t seekToPattern(const FluxMatcher& pattern);
nanoseconds_t seekToPattern(const FluxMatcher& pattern, const FluxMatcher*& matching);
bool readRawBit(nanoseconds_t clockPeriod);
std::vector<bool> readRawBits(unsigned count, nanoseconds_t clockPeriod);
std::vector<bool> readRawBits(const Fluxmap::Position& until, nanoseconds_t clockPeriod);
bool readRawBit();
std::vector<bool> readRawBits(unsigned count);
std::vector<bool> readRawBits(const Fluxmap::Position& until);
const std::vector<nanoseconds_t> intervals() const { return _intervals; };
private:
const Fluxmap& _fluxmap;
const uint8_t* _bytes;
const size_t _size;
Fluxmap::Position _pos;
std::vector<nanoseconds_t> _intervals;
const bool _isInterleaved;
};
#endif

View File

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

View File

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

View File

@@ -36,8 +36,7 @@ public:
usbSetDrive(_drive, high_density, indexMode);
usbSeek(track);
Bytes crunched = fluxmap.rawBytes().crunch();
return usbWrite(side, crunched);
return usbWrite(side, fluxmap.rawBytes());
}
private:

View File

@@ -50,9 +50,9 @@ public:
{
usbSetDrive(_drive, high_density, indexMode);
usbSeek(track);
Bytes crunched = usbRead(side, synced, revolutions * _oneRevolution);
Bytes data = usbRead(side, synced, revolutions * _oneRevolution);
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBytes(crunched.uncrunch());
fluxmap->appendBytes(data);
return fluxmap;
}

View File

@@ -12,7 +12,7 @@
#include <set>
#include <cassert>
typedef int nanoseconds_t;
typedef double nanoseconds_t;
class Bytes;
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 "image.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"
#include "fmt/format.h"
#include <algorithm>
#include <ctype.h>
std::map<std::string, ImageReader::Constructor> ImageReader::formats =
{
{".adf", ImageReader::createImgImageReader},
{".d81", ImageReader::createImgImageReader},
{".img", ImageReader::createImgImageReader},
{".ima", ImageReader::createImgImageReader},
};
static bool ends_with(const std::string& value, const std::string& ending)

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
#include "globals.h"
#include "image.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagewriter/imagewriter.h"
#include "fmt/format.h"
#include <iostream>
#include <fstream>
std::map<std::string, ImageWriter::Constructor> ImageWriter::formats =
{
@@ -63,6 +64,54 @@ 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\","
"\"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->headerStartTime,
sector->headerEndTime,
sector->dataStartTime,
sector->dataEndTime,
sector->position.bytes,
sector->data.size(),
Sector::statusToString(sector->status)
);
}
}
}
}
void ImageWriter::printMap()
{
int badSectors = 0;

View File

@@ -35,6 +35,7 @@ private:
public:
virtual void adjustGeometry();
void printMap();
void writeCsv(const std::string& filename);
virtual void writeImage() = 0;
protected:

View File

@@ -1,5 +1,4 @@
#include "globals.h"
#include "image.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
@@ -47,7 +46,7 @@ public:
if (sector)
{
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 "image.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"

322
lib/kmedian.cc Normal file
View File

@@ -0,0 +1,322 @@
#include "globals.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "protocol.h"
#include "kmedian.h"
#include <algorithm>
#include <numeric>
#include <math.h>
#include <limits.h>
/* Implement a O(kn log n) method for optimal 1D K-median. The K-median problem
* consists of finding k clusters so that the sum of absolute distances from
* each of the n points to the closest cluster is minimized.
*
* GRØNLUND, Allan, et al. Fast exact k-means, k-medians and Bregman divergence
* clustering in 1d. arXiv preprint arXiv:1701.07204, 2017.
*
* The points all have one of a very small range of values (compared to the
* number of points). Since k-medians clustering is either a point or the mean
* of two points, we can then do calculations with n being the number of
* distinct points, rather than the number of points, so that O(kn log n)
* becomes much quicker in concrete terms.
*
* Finding the median takes O(log n) time instead of O(1) time, which increases
* the constant factor in front of the kn log n term.
*/
class KCluster
{
public:
struct CDF
{
int pointsSoFar;
KValue sumToHere;
KValue thisValue;
};
int uniquePoints; /* number of unique points */
std::vector<struct CDF> cdf; /* cumulative sum of points */
KCluster(const std::vector<KValue>& inputPoints)
{
std::vector<KValue> points = inputPoints;
std::sort(points.begin(), points.end());
KValue current = NAN;
int count = 0;
KValue sum = 0.0;
for (KValue point : points)
{
if (point != current)
{
struct CDF empty = {0};
cdf.push_back(empty);
}
sum += point;
count++;
auto& thiscdf = cdf.back();
thiscdf.pointsSoFar = count;
thiscdf.sumToHere = sum;
thiscdf.thisValue = point;
current = point;
}
uniquePoints = cdf.size();
}
/* Determines the median point of all points between the ith category
* in cdf (inclusive), and the jth (exclusive). It's used to
* reconstruct clusters later.
*/
KValue medianPoint(int i, int j)
{
if (i >= j)
return 0;
int lowerCount = 0;
if (i > 0)
lowerCount = cdf[i-1].pointsSoFar;
int upperCount = cdf[j-1].pointsSoFar;
/* Note, this is not the index, but number of points start inclusive,
* which is why the -1 has to be turned into +1. */
KValue medianPoint = (lowerCount + upperCount + 1) / 2.0;
auto lowerMedian = std::lower_bound(cdf.begin(), cdf.end(), (int)medianPoint,
[&](const struct CDF& lhs, int rhs) {
return lhs.pointsSoFar < rhs;
}
);
if (((int)ceil(medianPoint) == (int)floor(medianPoint))
|| ((int)floor(medianPoint) != lowerMedian->pointsSoFar))
return lowerMedian->thisValue;
else
return (lowerMedian->thisValue + (lowerMedian+1)->thisValue) / 2.0;
}
/* Find the (location of the) minimum value of each row of an nxn matrix in
* n log n time, given that the matrix is monotone (by the definition of
* Grønlund et al.) and defined by a function M that takes row and column
* as parameters. All boundary values are closed, [minRow, maxRow]
* p. 197 of
* AGGARWAL, Alok, et al. Geometric applications of a matrix-searching
* algorithm. Algorithmica, 1987, 2.1-4: 195-208.
*/
void monotoneMatrixIndices(std::function<KValue(int, int)>& M,
int minRow, int maxRow, int minCol, int maxCol,
std::vector<int>& Tout,
std::vector<KValue>& Dout)
{
if (maxRow == minRow)
return;
int currentRow = minRow + (maxRow-minRow)/2;
/* Find the minimum column for this row. */
KValue minValue = INT_MAX;
int minHere = minCol;
for (int i = minCol; i<=maxCol; i++)
{
KValue v = M(currentRow, i);
if (v < minValue)
{
minValue = v;
minHere = i;
}
}
if (Tout[currentRow])
throw std::runtime_error("tried to set a variable already set");
Tout[currentRow] = minHere;
Dout[currentRow] = M(currentRow, minHere);
/* No lower row can have a minimum to the right of the current minimum.
* Recurse on that assumption. */
monotoneMatrixIndices(M, minRow, currentRow, minCol, minHere+1, Tout, Dout);
/* And no higher row can have a minimum to the left. */
monotoneMatrixIndices(M, currentRow+1, maxRow, minHere, maxCol, Tout, Dout);
}
/* If item is the first cdf value with count at
* or above i, returns the cumulative sum up to the ith entry of the
* underlying sorted points list.
*/
KValue cumulativeAt(std::vector<struct CDF>::iterator item, int i)
{
KValue sumBelow = 0.0;
int numPointsBelow = 0;
if (item != cdf.begin())
{
sumBelow = (item-1)->sumToHere;
numPointsBelow = (item-1)->pointsSoFar;
}
return sumBelow + item->thisValue*(i - numPointsBelow);
}
/* Grønlund et al.'s CC function for the K-median problem.
*
* CC(i,j) is the cost of grouping points_i...points_j into one cluster
* with the optimal cluster point (the median point). By programming
* convention, the interval is half-open and indexed from 0, unlike the
* paper's convention of closed intervals indexed from 1.
*
* Note: i and j are indices onto weighted_cdf. So e.g. if i = 0, j = 2 and
* weighted cdf is [[2, 0, 0], [4, 2, 1], [5, 2, 2]], then that is the cost
* of clustering all points between the one described by the zeroth weighted
* cdf entry, and up to (but not including) the last. In other words, it's
* CC([0, 0, 1, 1], 0, 5).
*/
KValue CC(int i, int j)
{
if (i >= j)
return 0;
int lowerCount = 0;
if (i > 0)
lowerCount = cdf[i-1].pointsSoFar;
int upperCount = cdf[j-1].pointsSoFar;
/* Note, this is not the index, but number of points start inclusive,
* which is why the -1 has to be turned into +1. */
KValue medianPoint = (lowerCount + upperCount + 1) / 2.0;
auto lowerMedian = std::lower_bound(cdf.begin(), cdf.end(), (int)medianPoint,
[&](const struct CDF& lhs, int rhs) {
return lhs.pointsSoFar < rhs;
}
);
KValue mu;
if (((int)ceil(medianPoint) == (int)floor(medianPoint))
|| ((int)floor(medianPoint) != lowerMedian->pointsSoFar))
mu = lowerMedian->thisValue;
else
mu = (lowerMedian->thisValue + (lowerMedian+1)->thisValue) / 2.0;
/* Lower part: entry i to median point between i and j, median excluded. */
int sumBelow = cumulativeAt(lowerMedian, (int)medianPoint);
if (i > 0)
sumBelow -= cdf[i-1].sumToHere;
/* Upper part: everything from the median up, including the median if it's a
* real point. */
int sumAbove = cdf[j-1].sumToHere - cumulativeAt(lowerMedian, (int)medianPoint);
return floor(medianPoint - lowerCount)*mu - sumBelow + sumAbove - ceil(upperCount - medianPoint)*mu;
}
/* D_previous is the D vector for (i-1) clusters, or empty if i < 2.
* It's possible to do this even faster (and more incomprehensibly).
* See Grønlund et al. for that. */
/* Calculate C_i[m][j] given D_previous = D[i-1]. p. 4 */
KValue C_i(int i, const std::vector<KValue>& D_previous, int m, int j)
{
KValue f;
if (i == 1)
f = CC(0, m);
else
f = D_previous[std::min(j, m)] + CC(j, m);
return f;
}
/* Calculates the optimal cluster centres for the points. */
std::vector<KValue> optimalKMedian(int numClusters)
{
std::vector<std::vector<int>> T(numClusters+1, std::vector<int>(uniquePoints+1, 0));
std::vector<std::vector<KValue>> D(numClusters+1, std::vector<KValue>(uniquePoints+1, 0.0));
for (int i=1; i<numClusters+1; i++)
{
/* Stop if we achieve optimal clustering with fewer clusters than the
* user asked for. */
std::vector<KValue>& lastD = D[i-1];
if ((i != 1) && (lastD.back() == 0.0))
continue;
std::function<KValue(int, int)> M = [&](int m, int j) {
return C_i(i, lastD, m, j);
};
monotoneMatrixIndices(M, i, uniquePoints+1, i-1, uniquePoints+1,
T[i], D[i]);
}
/* Backtrack. The last cluster has to encompass everything, so the
* rightmost boundary of the last cluster is the last point in the
* array, hence given by the last position of T. Then the previous
* cluster transition boundary is given by where that cluster starts,
* and so on.
*/
int currentClusteringRange = uniquePoints;
std::vector<KValue> centres;
for (int i=numClusters; i>0; i--)
{
int newClusteringRange = T[i][currentClusteringRange];
/* Reconstruct the cluster that's the median point between
* new_cluster_range and cur_clustering_range. */
centres.push_back(medianPoint(newClusteringRange, currentClusteringRange));
currentClusteringRange = newClusteringRange;
}
std::sort(centres.begin(), centres.end());
return centres;
}
};
static std::vector<KValue> getPointsFromFluxmap(const Fluxmap& fluxmap)
{
FluxmapReader fmr(fluxmap);
std::vector<KValue> points;
while (!fmr.eof())
{
KValue point = fmr.findEvent(F_BIT_PULSE);
points.push_back(point);
}
return points;
}
/* Analyses the fluxmap and determines the optimal cluster centres for it. The
* number of clusters is taken from the size of the output array. */
std::vector<KValue> optimalKMedian(const std::vector<KValue>& points, int clusters)
{
KCluster kcluster(points);
return kcluster.optimalKMedian(clusters);
}
std::vector<unsigned> optimalKMedian(const Fluxmap& fluxmap, int clusters)
{
std::vector<unsigned> ticks;
for (KValue t : optimalKMedian(getPointsFromFluxmap(fluxmap), clusters))
ticks.push_back(t);
return ticks;
}

10
lib/kmedian.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef KMEDIAN_H
#define KMEDIAN_H
typedef float KValue;
extern std::vector<KValue> optimalKMedian(const std::vector<KValue>& points, int clusters);
extern std::vector<unsigned> optimalKMedian(const Fluxmap& fluxmap, int clusters);
#endif

View File

@@ -12,12 +12,13 @@
#include "sectorset.h"
#include "visualiser.h"
#include "record.h"
#include "image.h"
#include "bytes.h"
#include "decoders/rawbits.h"
#include "track.h"
#include "imagewriter/imagewriter.h"
#include "fmt/format.h"
#include <iostream>
#include <fstream>
FlagGroup readerFlags
{
@@ -68,6 +69,11 @@ static SettableFlag highDensityFlag(
{ "--high-density", "--hd" },
"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;
void setReaderDefaultSource(const std::string& source)
@@ -85,13 +91,23 @@ void setReaderRevolutions(int 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()
{
std::cout << fmt::format("{0:>3}.{1}: ", physicalTrack, physicalSide) << std::flush;
fluxmap = fluxsource->readFlux(physicalTrack, physicalSide);
std::cout << fmt::format(
"{0} ms in {1} bytes\n",
int(fluxmap->duration()/1e6),
fluxmap->duration()/1e6,
fluxmap->bytes());
if (outputFluxSink)
outputFluxSink->writeFlux(physicalTrack, physicalSide, *fluxmap);
@@ -176,15 +192,29 @@ void readDiskCommand(AbstractDecoder& decoder)
decoder.decodeToSectors(*track);
std::cout << " ";
if (!track->sectors.empty())
{
std::cout << fmt::format("{} records, {} sectors; ",
track->rawrecords.size(),
track->sectors.size());
if (track->sectors.size() > 0)
std::cout << fmt::format("{:.2f}us clock ({:.0f}kHz); ",
track->sectors.begin()->clock / 1000.0,
1000000.0 / track->sectors.begin()->clock);
const std::vector<nanoseconds_t>& intervals = track->intervals;
bool first = true;
for (nanoseconds_t i : intervals)
{
if (!first)
std::cout << "/";
first = false;
std::cout << fmt::format("{:.2f}", i / 1000.0);
}
std::cout << " us or ";
first = true;
for (nanoseconds_t i : intervals)
{
if (!first)
std::cout << "/";
first = false;
std::cout << fmt::format("{:.0f}", 1000000.0 / i);
}
std::cout << fmt::format(" kHz interval\n");
for (auto& sector : track->sectors)
{
@@ -193,9 +223,12 @@ void readDiskCommand(AbstractDecoder& decoder)
}
bool hasBadSectors = false;
std::set<unsigned> requiredSectors = decoder.requiredSectors(*track);
for (const auto& i : readSectors)
{
const auto& sector = i.second;
requiredSectors.erase(sector->logicalSector);
if (sector->status != Sector::OK)
{
std::cout << std::endl
@@ -204,6 +237,12 @@ void readDiskCommand(AbstractDecoder& decoder)
hasBadSectors = true;
}
}
for (unsigned logicalSector : requiredSectors)
{
std::cout << "\n"
<< " Required sector " << logicalSector << " missing; ";
hasBadSectors = true;
}
if (hasBadSectors)
failures = false;
@@ -213,7 +252,6 @@ void readDiskCommand(AbstractDecoder& decoder)
if (!hasBadSectors)
break;
}
if (!track->fluxsource->retryable())
break;
@@ -229,8 +267,7 @@ void readDiskCommand(AbstractDecoder& decoder)
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (auto& record : track->rawrecords)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record.position.ns() / 1000.0, record.clock / 1000.0);
std::cout << fmt::format("I+{:.2f}us\n", record.position.ns() / 1000.0);
hexdump(std::cout, record.data);
std::cout << std::endl;
}
@@ -239,13 +276,13 @@ void readDiskCommand(AbstractDecoder& decoder)
if (dumpSectors)
{
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\n",
sector->logicalTrack, sector->logicalSide, sector->logicalSector,
sector->position.ns() / 1000.0, sector->clock / 1000.0);
hexdump(std::cout, sector->data);
std::cout << fmt::format("{}.{:02}.{:02}: I+{:.2f}us: status {}\n",
sector.logicalTrack, sector.logicalSide, sector.logicalSector,
sector.position.ns() / 1000.0,
sector.status);
hexdump(std::cout, sector.data);
std::cout << std::endl;
}
}
@@ -274,7 +311,7 @@ void readDiskCommand(AbstractDecoder& decoder)
if (!visualise.get().empty())
visualiseSectorsToFile(allSectors, visualise.get());
writeSectorsToFile(allSectors, outputSpec);
if (failures)
std::cerr << "Warning: some sectors could not be decoded." << std::endl;

View File

@@ -9,7 +9,7 @@ public:
RawRecord() {}
Fluxmap::Position position;
nanoseconds_t clock = 0;
std::vector<nanoseconds_t> intervals;
int physicalTrack = 0;
int physicalSide = 0;
Bytes data;

View File

@@ -26,7 +26,7 @@ public:
Status status = Status::INTERNAL_ERROR;
Fluxmap::Position position;
nanoseconds_t clock = 0;
std::vector<nanoseconds_t> intervals;
nanoseconds_t headerStartTime = 0;
nanoseconds_t headerEndTime = 0;
nanoseconds_t dataStartTime = 0;

View File

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

View File

@@ -10,8 +10,9 @@ enum
FLUX_VERSION_0, /* without properties table */
FLUX_VERSION_1,
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);

View File

@@ -20,6 +20,7 @@ public:
unsigned physicalSide;
std::shared_ptr<FluxSource> fluxsource;
std::unique_ptr<Fluxmap> fluxmap;
std::vector<nanoseconds_t> intervals;
std::vector<RawRecord> rawrecords;
std::vector<Sector> sectors;

View File

@@ -3,7 +3,6 @@
#include "protocol.h"
#include "fluxmap.h"
#include "bytes.h"
#include "common/crunch.h"
#include <libusb.h>
#include "fmt/format.h"
@@ -154,10 +153,13 @@ nanoseconds_t usbGetRotationalPeriod(void)
static 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() << "data transfer failed: " << usberror(i);
Error() << fmt::format("data transfer failed at {} bytes: {}", len, usberror(i));
return len;
}
@@ -276,7 +278,6 @@ void usbWrite(int side, const Bytes& bytes)
((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);

View File

@@ -1,6 +1,5 @@
#define _USE_MATH_DEFINES
#include "globals.h"
#include "image.h"
#include "sector.h"
#include "sectorset.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)
{
start %= period*1000000;
end %= period*1000000;
start = fmod(start, period*1000000.0);
end = fmod(end, period*1000000.0);
if (end < start)
end += period*1000000;

View File

@@ -9,13 +9,13 @@
#include "encoders/encoders.h"
#include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h"
#include "imagereader/imagereader.h"
#include "fmt/format.h"
#include "record.h"
#include "image.h"
#include "sector.h"
#include "sectorset.h"
FlagGroup writerFlags { &hardwareFluxSourceFlags, &hardwareFluxSinkFlags };
FlagGroup writerFlags { &hardwareFluxSourceFlags, &sqliteFluxSinkFlags, &hardwareFluxSinkFlags };
static DataSpecFlag dest(
{ "--dest", "-d" },
@@ -43,6 +43,11 @@ void setWriterDefaultInput(const std::string& input)
::input.set(input);
}
static SectorSet readSectorsFromFile(const ImageSpec& spec)
{
return ImageReader::create(spec)->readImage();
}
void writeTracks(
const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer)
{
@@ -53,19 +58,7 @@ void writeTracks(
setHardwareFluxSourceDensity(highDensityFlag);
setHardwareFluxSinkDensity(highDensityFlag);
if (!spec.filename.empty())
{
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);
}
);
}
std::shared_ptr<FluxSink> fluxSink = FluxSink::create(spec);
for (const auto& location : spec.locations)
{
@@ -73,31 +66,16 @@ void writeTracks(
std::unique_ptr<Fluxmap> fluxmap = producer(location.track, location.side);
if (!fluxmap)
{
if (!outdb)
{
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;
/* Create an empty fluxmap for writing. */
fluxmap.reset(new Fluxmap());
}
/* 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

@@ -174,7 +174,6 @@ buildlibrary libbackend.a \
arch/victor9k/decoder.cc \
arch/zilogmcz/decoder.cc \
lib/bytes.cc \
lib/common/crunch.c \
lib/crc.cc \
lib/dataspec.cc \
lib/decoders/decoders.cc \
@@ -193,7 +192,7 @@ buildlibrary libbackend.a \
lib/fluxsource/streamfluxsource.cc \
lib/globals.cc \
lib/hexdump.cc \
lib/image.cc \
lib/kmedian.cc \
lib/ldbs.cc \
lib/reader.cc \
lib/sector.cc \
@@ -260,14 +259,15 @@ buildsimpleprogram brother240tool \
libemu.a \
libfmt.a \
runtest amiga-test tests/amiga.cc
runtest bitaccumulator-test tests/bitaccumulator.cc
runtest bytes-test tests/bytes.cc
runtest compression-test tests/compression.cc
runtest crunch-test tests/crunch.cc
runtest dataspec-test tests/dataspec.cc
runtest flags-test tests/flags.cc
runtest fluxpattern-test tests/fluxpattern.cc
runtest fmmfm-test tests/fmmfm.cc
runtest kmedian-test tests/kmedian.cc
runtest kryoflux-test tests/kryoflux.cc
runtest ldbs-test tests/ldbs.cc
runtest amiga-test tests/amiga.cc

View File

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

View File

@@ -81,14 +81,14 @@ int mainConvertFluxToAu(int argc, const char* argv[])
while (!fmr.eof())
{
unsigned ticks;
int op = fmr.readOpcode(ticks);
if (op == -1)
uint8_t bits = fmr.getNextEvent(ticks);
if (fmr.eof())
break;
timestamp += ticks;
if (op == F_OP_PULSE)
if (bits & F_BIT_PULSE)
data[timestamp*channels + 0] = 0x7f;
if (withIndex && (op == F_OP_INDEX))
if (withIndex && (bits & F_BIT_INDEX))
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"
);
static SettableFlag indexedMode(
{ "--indexed", "-i" },
"align data to track boundaries"
);
static SettableFlag singleSided(
{ "--single-sided", "-s" },
"only emit side 0"
@@ -45,14 +50,18 @@ static void write_le32(uint8_t dest[4], uint32_t v)
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)
{
if (fortyTrackMode)
track /= 2;
if (singleSided)
return track;
else
return (track << 1) | side;
return (track << 1) | side;
}
int mainConvertFluxToScp(int argc, const char* argv[])
@@ -90,7 +99,8 @@ int mainConvertFluxToScp(int argc, const char* argv[])
fileheader.revolutions = 5;
fileheader.start_track = 0;
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.heads = singleSided ? 1 : 0;
@@ -104,9 +114,15 @@ int mainConvertFluxToScp(int argc, const char* argv[])
for (int side = 0; side <= maxside; 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);
if (fluxmap->bytes() == 0)
{
std::cout << "missing\n";
continue;
}
ScpTrack trackheader = {0};
trackheader.track_id[0] = 'T';
trackheader.track_id[1] = 'R';
@@ -117,6 +133,9 @@ int mainConvertFluxToScp(int argc, const char* argv[])
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);
if (indexedMode)
fmr.findEvent(F_BIT_INDEX);
int revolution = 0;
unsigned revTicks = 0;
unsigned totalTicks = 0;
@@ -125,50 +144,41 @@ int mainConvertFluxToScp(int argc, const char* argv[])
while (revolution < 5)
{
unsigned ticks;
int opcode = fmr.readOpcode(ticks);
if (ticks)
{
ticksSinceLastPulse += ticks;
totalTicks += ticks;
revTicks += ticks;
}
uint8_t bits = fmr.getNextEvent(ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
revTicks += ticks;
switch (opcode)
{
case -1: /* end of flux, treat like an index marker */
case F_OP_INDEX:
{
auto* revheader = &trackheader.revolution[revolution];
write_le32(revheader->offset, startOffset + sizeof(ScpTrack));
write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2);
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
revolution++;
revheader++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
break;
}
if (fmr.eof() || (bits & F_BIT_INDEX))
{
auto* revheader = &trackheader.revolution[revolution];
write_le32(revheader->offset, startOffset + sizeof(ScpTrack));
write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2);
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
revolution++;
revheader++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
}
case F_OP_PULSE:
{
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000)
{
fluxdataWriter.write_be16(0);
t -= 0x10000;
}
fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0;
break;
}
}
if (bits & F_BIT_PULSE)
{
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000)
{
fluxdataWriter.write_be16(0);
t -= 0x10000;
}
fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0;
}
}
write_le32(fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
trackdataWriter += fluxdata;
std::cout << fmt::format("{} ms in {} bytes\n",
std::cout << fmt::format("{:.3f} ms in {} bytes\n",
totalTicks * MS_PER_TICK,
fluxdata.size());
}
@@ -176,6 +186,13 @@ int mainConvertFluxToScp(int argc, const char* argv[])
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::ofstream of(filenames[1], std::ios::out | std::ios::binary);
if (!of.is_open())

View File

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

View File

@@ -4,13 +4,13 @@
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "decoders/decoders.h"
#include "image.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "record.h"
#include "sector.h"
#include "track.h"
#include "fmt/format.h"
#include "kmedian.h"
static FlagGroup flags { &readerFlags };
@@ -51,6 +51,11 @@ static DoubleFlag signalLevelFactor(
"Clock detection signal level (min + (max-min)*factor).",
0.05);
static IntFlag bands(
{ "--bands" },
"Number of bands to use for k-median interval classification.",
3);
void setDecoderManualClockRate(double clockrate_us)
{
manualClockRate.setDefaultValue(clockrate_us);
@@ -73,7 +78,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
while (!fr.eof())
{
unsigned interval = fr.readNextMatchingOpcode(F_OP_PULSE);
unsigned interval = fr.findEvent(F_BIT_PULSE);
if (interval > 0xff)
continue;
buckets[interval]++;
@@ -84,54 +89,6 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
uint32_t noise_floor = min + (max-min)*noiseFloorFactor;
uint32_t signal_level = min + (max-min)*signalLevelFactor;
/* Find a point solidly within the first pulse. */
int pulseindex = 0;
while (pulseindex < 256)
{
if (buckets[pulseindex] > signal_level)
break;
pulseindex++;
}
if (pulseindex == -1)
return 0;
/* Find the upper and lower bounds of the pulse. */
int peaklo = pulseindex;
while (peaklo > 0)
{
if (buckets[peaklo] < noise_floor)
break;
peaklo--;
}
int peakhi = pulseindex;
while (peakhi < 255)
{
if (buckets[peakhi] < noise_floor)
break;
peakhi++;
}
/* Find the total accumulated size of the pulse. */
uint32_t total_size = 0;
for (int i = peaklo; i < peakhi; i++)
total_size += buckets[i];
/* Now find the median. */
uint32_t count = 0;
int median = peaklo;
while (median < peakhi)
{
count += buckets[median];
if (count > (total_size/2))
break;
median++;
}
std::cout << "\nClock detection histogram:" << std::endl;
bool skipping = true;
@@ -156,23 +113,24 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
s += BLOCK_ELEMENTS[8];
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 << fmt::format("Noise floor: {}", noise_floor) << std::endl;
std::cout << fmt::format("Signal level: {}", signal_level) << std::endl;
std::cout << fmt::format("Peak start: {} ({:.2f} us)", peaklo, peaklo*US_PER_TICK) << std::endl;
std::cout << fmt::format("Peak end: {} ({:.2f} us)", peakhi, peakhi*US_PER_TICK) << std::endl;
std::cout << fmt::format("Median: {} ({:.2f} us)", median, median*US_PER_TICK) << std::endl;
/*
* Okay, the median should now be a good candidate for the (or a) clock.
* How this maps onto the actual clock rate depends on the encoding.
*/
std::vector<unsigned> centres = optimalKMedian(fluxmap, bands);
for (int i=0; i<bands; i++)
std::cout << fmt::format("Band #{}: {} {:.2f} us\n",
i, centres[i], centres[i] * US_PER_TICK);
return median * NS_PER_TICK;
return centres[0] * NS_PER_TICK;
}
int mainInspect(int argc, const char* argv[])
@@ -186,10 +144,16 @@ int mainInspect(int argc, const char* argv[])
auto& track = *tracks.begin();
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);
std::cout << fmt::format("{:.2f} us clock detected.", (double)clockPeriod/1000.0) << std::flush;
FluxmapReader fmr(*track->fluxmap);
FluxmapReader fmr(*track->fluxmap, bands, false);
fmr.seek(seekFlag*1000000.0);
if (dumpFluxFlag)
@@ -209,7 +173,7 @@ int mainInspect(int argc, const char* argv[])
nanoseconds_t lasttransition = 0;
while (!fmr.eof())
{
ticks += fmr.readNextMatchingOpcode(F_OP_PULSE);
ticks += fmr.findEvent(F_BIT_PULSE);
nanoseconds_t transition = ticks*NS_PER_TICK;
nanoseconds_t next;
@@ -261,7 +225,7 @@ int mainInspect(int argc, const char* argv[])
{
if (fmr.eof())
break;
bool b = fmr.readRawBit(clockPeriod);
bool b = fmr.readRawBit();
std::cout << (b ? 'X' : '-');
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,32 +3,37 @@
#include "reader.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "image.h"
#include "sector.h"
#include "sectorset.h"
#include "record.h"
#include "dataspec.h"
#include "ibm/ibm.h"
#include "fmt/format.h"
static FlagGroup flags { &readerFlags };
static IntFlag sectorIdBase(
{ "--sector-id-base" },
{ "--ibm-sector-id-base" },
"Sector ID of the first sector.",
1);
static BoolFlag ignoreSideByte(
{ "--ignore-side-byte" },
{ "--ibm-ignore-side-byte" },
"Ignore the side byte in the sector ID, and use the physical side instead.",
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[])
{
setReaderDefaultSource(":t=0-79:s=0-1");
setReaderDefaultOutput("ibm.img");
flags.parseFlags(argc, argv);
IbmDecoder decoder(sectorIdBase, ignoreSideByte);
IbmDecoder decoder(sectorIdBase, ignoreSideByte, requiredSectors);
readDiskCommand(decoder);
return 0;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ static int endSide;
static void syntax()
{
std::cout << "Syntax: fluxengine convert cwftoflux <cwffile> <fluxfile>\n";
std::cout << "Syntax: fluxengine convert scptoflux <scpfile> <fluxfile>\n";
exit(0);
}
@@ -28,15 +28,11 @@ static void check_for_error()
static int trackno(int strack)
{
if (startSide == endSide)
return strack;
return strack >> 1;
}
static int headno(int strack)
{
if (startSide == endSide)
return startSide;
return strack & 1;
}
@@ -65,6 +61,8 @@ static void read_header()
static void read_track(int strack)
{
uint32_t offset = Bytes(header.track[strack], 4).reader().read_le32();
if (offset == 0)
return;
ScpTrack trackheader;
inputFile.seekg(offset, std::ios::beg);
@@ -112,7 +110,7 @@ static void read_track(int strack)
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());
sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap);
}

View File

@@ -6,7 +6,7 @@
static sqlite3* db;
static void update_version_1_to_2()
static void update_version_1_to_3()
{
for (const auto i : sqlFindFlux(db))
{
@@ -33,6 +33,44 @@ static void update_version_1_to_2()
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[])
{
if (argc != 2)
@@ -71,14 +109,24 @@ int mainUpgradeFluxFile(int argc, const char* argv[])
if (version == FLUX_VERSION_1)
{
std::cout << "Upgrading to version 2\n";
std::cout << "Upgrading to version 3\n";
sqlStmt(db, "BEGIN;");
update_version_1_to_2();
version = FLUX_VERSION_2;
update_version_1_to_3();
version = FLUX_VERSION_3;
sqlWriteIntProperty(db, "version", version);
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";
sqlStmt(db, "VACUUM;");
std::cout << "Upgrade done\n";

View File

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

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