Compare commits

...

82 Commits

Author SHA1 Message Date
David Given
15e6d4959e Adjust Micropolis documentation. 2020-10-11 18:49:00 +02:00
David Given
41216fd1cd Merge pull request #191 from ejona86/micropolis
Add Micropolis decoder
2020-10-11 18:52:31 +02:00
Eric Anderson
b8786866db Fix carry handling in Micropolis checksum
Two bugs make a right, mostly. The very last carry should be ignored and
when adding the low and high bytes they commonly would commonly produce
a carry which was being ignored. On the test disks these two bugs
cancelled themselves out, except only for track 1, side 1, sector 0. I
had noticed it was suspicously off-by-one earlier for both test disks,
but the cause wasn't obvious.

On some old 1980s disks, with real data, it changed number of bad
sectors from 224 (out of 2464) to 5 for one disk and 81 to 3 for
another.
2020-10-04 19:55:25 -05:00
Eric Anderson
82bd1bead4 Add Micropolis decoder
Resolves #187
2020-10-03 13:05:06 -05:00
David Given
3b02bc8cf1 Merge pull request #183 from davidgiven/ds990
Add support for TI DS990 disks.
2020-09-10 21:19:10 +02:00
David Given
c7e48a7e76 Unicorn the DS990 format. 2020-09-10 20:10:54 +02:00
David Given
77d125c03d Typo fix. 2020-08-22 12:38:10 +02:00
David Given
8aa52aeefd Add documentation for the DS990. 2020-08-22 12:37:11 +02:00
David Given
0bab038454 Make the DS990 encoder work, maybe? 2020-08-22 00:36:48 +02:00
David Given
6c3b49f4d0 Add the DS990 encoder skeleton. 2020-08-21 23:07:35 +02:00
David Given
03dd689f17 Make the DS990 decoder work. 2020-08-21 00:32:36 +02:00
David Given
c375c948c0 Add boilerplate for the TI DS990 decoder. 2020-08-20 22:41:14 +02:00
David Given
cbcf457ce3 Merge pull request #182 from davidgiven/usb
Add support for multiple simultaneously connected FluxEngines.
2020-08-10 23:32:16 +02:00
David Given
4855f825e2 Show serial number on device connection, and improve the device listing a bit. 2020-08-10 23:16:04 +02:00
David Given
85bc1637f2 Document the use of multiple FluxEngines. 2020-08-10 23:12:33 +02:00
David Given
73398b83a9 Add support for specifying which FluxEngine you want to use with the --devices
parameter.
2020-08-10 22:36:47 +02:00
David Given
2727e66d40 Allow multiple USB implementations. 2020-08-09 22:33:54 +02:00
David Given
8b6be5a501 Move usb.{cc,h} into its own directory. 2020-08-09 21:14:09 +02:00
David Given
4fee29307c Refactor the USB stuff to allow for multiple USB implementations. 2020-08-08 14:52:11 +01:00
David Given
35f8249c67 Merge pull request #178 from davidgiven/mac
Add support for exporting to DiskCopy 4.2 Mac disk image
2020-08-02 17:42:48 +01:00
David Given
d1467a14b8 Merge. 2020-08-02 18:24:15 +02:00
David Given
3e6b9eb74d Document the .diskcopy file format. 2020-08-02 18:22:30 +02:00
David Given
ce2e8fb4b5 First draft of the DiskCopy 4.2 image writer. 2020-07-30 20:21:49 +01:00
David Given
7eaa75c05d Merge pull request #177 from davidgiven/mac
Make Mac double-sided disks work.
2020-07-29 00:38:28 +02:00
David Given
e86de4483a Fix stupid bug where the mac decoder was looking at the wrong bit for the side
data.
2020-07-28 01:25:50 +02:00
David Given
203a74713f Merge pull request #175 from davidgiven/scp
Make SCP import and export properly handle single-sided images.
2020-06-30 11:19:48 +02:00
David Given
59ed2a6793 Make SCP import and export properly handle single-sided images. 2020-06-26 20:25:56 +02:00
David Given
a03283ce64 Merge pull request #174 from davidgiven/scp
Fix the SCP exporter.
2020-06-26 15:40:41 +02:00
David Given
984cdaeb03 Make nanoseconds_t a double to prevent overflow on very large numbers of
revolutions (I've just seen a flux file with 50).
2020-06-26 14:47:25 +02:00
David Given
a1ed4a9171 Fill out the SCP checksum correctly, and add a --indexed mode which causes data
prior to the first index mark to be discarded and sets the INDEX bit in the
header.
2020-06-26 12:20:49 +02:00
David Given
93caf8e549 Merge pull request #172 from davidgiven/b169
Fix a crash when decoding MX disks if advanceToNextRecord finds no records in a track.
2020-06-25 22:15:00 +02:00
David Given
3841942153 Fix a crash when decoding MX disks if advanceToNextRecord finds no records in a
track.
2020-06-25 21:56:33 +02:00
David Given
5706877b67 Merge pull request #171 from davidgiven/newsampler
Manually merge in jboone's new sampler from #164
2020-06-25 21:16:11 +02:00
David Given
d60900262b Remove the pulse converters from the sampler (the new sampler doesn't require
them). Update firmware.
2020-06-25 21:07:58 +02:00
David Given
54ea34400b Merge in jboone's updated sampler branch. 2020-06-25 21:01:35 +02:00
Jared Boone
db2ab8841a Update Sampler.v, moving clock domain crossing to FIFO interface.
Hopefully, I unscrewed the tab/space and line ending mismatches to minimize the diff.
2020-05-30 21:31:17 -07:00
David Given
adba93ae0a Merge pull request #163 from davidgiven/brother120
Untested Brother 120kB write support.
2020-05-24 01:32:28 +02:00
David Given
98587d04a7 Merge from trunk. 2020-05-24 00:56:17 +02:00
David Given
0051b64648 Merge pull request #167 from vanbogaertetienne/trs80fix
Typo fix in FM_TRS80DAM2_PATTERN
2020-05-24 00:47:12 +02:00
vanbogaertetienne
603009ba15 Typo fix in FM_TRS80DAM2_PATTERN 2020-05-22 20:46:48 +02:00
Jared Boone
adb9809692 Simplify Sampler. 2020-05-20 11:34:29 -07:00
David Given
06eb10d2a0 Merge. 2020-05-16 10:38:17 +02:00
David Given
2244299bd9 Add a bias parameter to allow the entire Brother format to be moved around on
the disk.
2020-05-16 10:37:48 +02:00
David Given
6ca06ecafb Fix several embarassingly stupid bugs in the brother 120kB encoder code path. 2020-05-14 10:41:25 +02:00
David Given
9a5958f80b Prevent OOB when appending no bytes to a fluxmap. 2020-05-14 10:40:49 +02:00
David Given
2b53ac057c Fix some bugs which allow erasing tracks with F_FRAME_WRITE_CMD to work again.
(F_FRAME_ERASE_CMD always worked.)
2020-05-13 23:45:58 +02:00
David Given
5deba8af41 Untested Brother 120kB write support. 2020-05-13 14:49:06 +02:00
David Given
3c54a663b8 Merge pull request #157 from davidgiven/amigacorruption
Fix some issues causing corruption when reading Amiga disks
2020-04-09 00:16:38 +02:00
David Given
1fd65452c4 Typo fix. 2020-04-08 23:37:08 +02:00
David Given
30646ccb07 Fix an Amiga decoder bug where truncated sectors would be considered valid (the
Amiga checksum algorithm is weak and zero bytes don't contribute to the
checksum).
2020-04-08 23:35:55 +02:00
David Given
5be7249a30 Merge pull request #155 from davidgiven/amigawriter
Fix stray bytes at the end of images
2020-04-07 23:13:23 +02:00
David Given
067af18103 When writing images, use the sector size in the spec rather than the actual
data size, to avoid problems with multipart formats like the Amiga.
2020-04-07 23:02:47 +02:00
David Given
8dbd2a72a7 Merge pull request #150 from davidgiven/sixbit
Fix the new sampler and sequencer
2020-04-03 23:54:29 +02:00
David Given
c29e131a3b Convert the IBM format back into a unicorn now I've fixed it. 2020-04-03 23:49:01 +02:00
David Given
a9e30c1e49 Fix an off-by-one error in the sequencer that should have it generating correct
sequences.
2020-04-03 22:58:51 +02:00
David Given
972c8c6b61 Fix off-by-one sampler error, so now the clock rates are right again. 2020-04-03 22:27:33 +02:00
David Given
2007ff7546 Fix merge wibble. 2020-04-03 21:49:26 +02:00
David Given
64694580cd Remember to bump the protocol number after the bytecode change. 2020-04-03 21:46:51 +02:00
David Given
deaab94494 Merge pull request #146 from davidgiven/sixbit
Switch to a simplified encoding with a six-bit timer.
2020-04-03 00:45:21 +02:00
David Given
1509e1f89d Document the new bytecode format. 2020-04-03 00:38:20 +02:00
David Given
29e1ddc2ff Bytecode upgrades always produce the latest version; we don't want to upgrade
1->2->3 (although that specific case is harmless, by accident).
2020-04-03 00:36:55 +02:00
David Given
1fe6434563 Something is wrong with the IBM PC writer, I don't know what. Mark it as a
dinosaur again.
2020-04-03 00:27:12 +02:00
David Given
0367b7e77d Merge from trunk. 2020-04-01 00:06:35 +02:00
David Given
e6da85bf64 Merge pull request #145 from davidgiven/writereport
Add a machine readable read report.
2020-04-01 00:04:54 +02:00
David Given
cd19fcdadd The CSV report now writes records for every sector in the output map, even the
missing ones.
2020-03-31 00:14:23 +02:00
David Given
1954f02cfb Don't reset the device on startup because it confuses Linux. 2020-03-30 22:23:18 +01:00
David Given
39b23200b0 Fix missing flag dependency. 2020-03-29 23:12:12 +02:00
David Given
0644d6d965 Remove some stray tracing (which was causing problems). Fix a potential problem
where sampleclock posedges could be lost in the sequencer.
2020-03-29 23:11:53 +02:00
David Given
a075694d8e Rewrite the sequencer to work with the new six bit bytecode. Fiddle with the
USB stuff a lot in an attempt to resolve the weird packet loss issue.
2020-03-29 15:10:35 +02:00
David Given
b1ea5a9a35 Rework the writer to use a fluxsink rather than just writing stuff directly. 2020-03-29 15:08:45 +02:00
David Given
00087cbb6b Fix a catastrophic DMA setup bug which was causing (probably) every other byte
of data to be mixed up with every other byte... but as every other byte was a
0x80 we never noticed up until now.
2020-03-20 00:06:58 +01:00
David Given
1b48ea20c4 Remove the cruncher. 2020-03-20 00:06:07 +01:00
David Given
3d0f019fc4 Replace the sampler with one using the new simplified bytecode. 2020-03-19 23:39:23 +01:00
David Given
a08bfc183f Display the tick value along with each interval in the histogram. 2020-03-19 22:16:07 +01:00
David Given
c5aef9b051 Annotate inspect to display USB bandwidth. 2020-03-15 13:47:17 +00:00
David Given
fc2655ecd6 Rework the bytecode format to use a much simplified setup: a six-bit timer with
the top two bits reserved for pulse and index state. This is actually smaller,
bandwidth-wise, than the old version, and may be smaller than the crunched
version.
2020-03-14 18:58:43 +00:00
David Given
a737c723d3 Make sure to update before installing packages. 2020-03-14 14:47:04 +00:00
David Given
37aa8b62b0 Add a --write-csv=X option to the reader to dump the sector status map as a
machine-readable file.
2020-03-14 14:35:19 +00:00
David Given
a401173f6d Teach the Amiga decoder how many sectors to expect on each track. 2020-03-09 12:54:29 +00:00
David Given
ce76dc4279 Merge pull request #140 from davidgiven/sectors
Fix a couple of annoying but minor issues
2020-02-28 00:08:11 +01:00
David Given
1025bd857b Don't crashloop if the USB's not connected, as it causes the drives to be
constantly reprobed (which runs the motor).
2020-02-27 22:32:27 +01:00
David Given
025802b2d0 Count required sectors correctly on mac disks. 2020-02-27 22:30:05 +01:00
98 changed files with 2259 additions and 1530 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,254 +1,254 @@
:4000000000800020110000006112000061120000064A08B5136843F020031360044B1A6803F53F5302331A6001F038F9E8460040FA46004010B5054C237833B9044B13B10E
:400040000448AFF300800123237010BD6881FF1F00000000E03A0000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000027
:400080006C81FF1FE03A0000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1FA2
:400000000080002011000000A10F0000A10F0000064A08B5136843F020031360044B1A6803F53F5302331A6000F0D8FFE8460040FA46004010B5054C237833B9044B13B1EF
:400040000448AFF300800123237010BD6881FF1F0000000020380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF00000000E9
:400080006C81FF1F20380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1F64
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080024B012200205A7293
:4001000002F072BA8881FF1F10B5C4B2204601F087FA0128FAD110BD08B572B60F4B0F49DA680132DA601A690132C82A08BF00221A615A6918690132A72A08BF00224A6178
:400140005B69002B0CBF02230023002814BF184643F0010002F0B0FF62B608BD8881FF1F38B50446C5B2284602F0E0F9062002F0FDFB44F00200C0B202F0D8F9062002F0D1
:40018000F5FB284602F0D2F9BDE83840062002F0D7BB10B5642402F0C3F930B90120FFF7DFFF013CF7D1204610BD012010BD70B5C4B220460E4601F03BFA314605460246D9
:4001C000204601F0F7FA204601F02AFA0128FAD0284670BD38B5044D0024285D013402F075FB402CF9D138BDA081FF1F08B502F08FFD002002F098FD02F0AAFD02F0B4FDA8
:4002000080B208BD10B50446012002F0A7F9642002F096FBFFF7EAFF2080002002F09EF9642002F08DFBFFF7E1FF608010BD08B502F09AFE002002F0A3FE02F0B5FE02F0C4
:40024000BFFE80B208BD10B50446FFF7A2FF322002F076FBFFF7EBFF20800020FFF780FF322002F06DFBFFF7E2FF608010BD0FB400B593B014AB53F8042B402102A8019397
:4002800003F016F802A802F0B6F902F0C0F913B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF72FFF62782146BDE81040042001F00CBAF43A000007B50023BF
:4002C000ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8583043B1002002F014F9002002F0A7FE002384F8583010BD00BF8881FF1F63
:4003000038B5104D837895F85B2004469A4204D0FFF7E4FF002385F85E302368C5F859302279094B1A71A378002B14BF0220012002F086FEE07802F07DFE2079BDE83840D7
:4003400002F0B4BE8881FF1FE181FF1F38B50D4C94F8585065B904F15900FFF7D1FF012002F0D8F84FF47A7002F0EAFA84F85E50E3682366012384F85830BDE8384002F0AB
:400380001DBB00BF8881FF1FF8B51E4C0646FFF7DDFF94F85E3003B15EB91B48FFF767FFFFF7F7FE0120002384F85E00636602F0DDFA3246616E1548FFF759FF114D002794
:4003C000636E9E4216D002F0ABF800B16F66636E9E4205DD0020FFF7C3FE6B6E013305E005DA0120FFF7BCFE6B6E013B6B6602F0E5FAE5E7322002F0A3FABDE8F8400448B2
:40040000FFF735BF8881FF1F013B0000083B0000253B00002DE9F04F9BB062B602F038FB9B49042002F05CFB9A4802F085F89A4802F028FE994802F0B9F802F009FD02F0E5
:40044000DBFB002002F0FCFD02F0D4F80221002001F09CF803210846914C02F08FFA002384F85B30FFF772FFFFF793FE84F86800FFF734FF012384F85B30FFF767FFFFF7A9
:4004800088FE84F86900FFF729FF94F86800854B854994F869202546002A14BF0A461A46002808BF19468148FFF7E1FEA24602F085FA94F8583043B12A6EEB689B1A41F2D4
:4004C0008832934201D9FFF709FF01F097F818B97748FFF7CCFE04E001F096F80028F7D109E001F08BF80028FBD07248FFF7BFFE032001F0B3F9032001F092F80128D6D187
:400500006D48FFF7FDFE6D490320FFF750FE94F86A106B48FFF7ABFE94F86A30023B142B00F27684DFE813F0150074041E00740424007404480074046B007404D800740496
:40054000FE017404AC037404CC037404D3037404ED0303238DF828308DF829300C238DF82A30CAE394F86C00FFF70EFF554BC1E3FFF7ECFE00236372E068627A02F0FF018F
:4005800032B9EB681B1AB3F57A7FF6DD0B460AE04BB100228AF80920DAF80C10627A12B9EB685B1AFAE707228DF8282004228DF82920ADF82A30A0E30220FFF7A5FD0027C5
:4005C0000DF1280902F0FAF94FF480780026C8EB07039A1906F809200136402EF9D10220FFF792FD32464946022001F071F8B8F10108EBD10137402FE4D1334B42E04FF0AD
:400600000109002702F0DAF94FF00008012001F025F9012001F004F80128FAD10DF1280B4022594601F0C6F8012000F0F9FF0028FAD1064608EB07030593059B1BF80620B4
:400640003344DBB2934209D08DE80C003946334642461E48FFF70BFE4FF000090136402EEBD108F10108B8F5807FCFD10137402FC8D149461648FFF7FAFDB9F1000F00F0A8
:400680005981144B1B8809A8ADF8243036E300BF19010000F900000091000000C50000008881FF1F373B0000333B00003A3B0000523B0000653B0000E181FF1FF281FF1FB9
:4006C0006F3B0000E43A0000E63A00007E3B00009A3B0000E83A000094F86C0001F03EFF606EFFF751FE02F04FFD934B934F1A78002602F0FB021A701A7842F001021A70A9
:400700001A7802F0FE021A701A7802F0FE021A7002F03EFD0220FFF7F7FC41F6FF734FF480420121022002F091FC84F8AA0002F0B3F8B8550136402EF9D1DFF82082002616
:4007400008F1990B1FFA8BF70136402E14BF3246002218F8010F22440623127E402102F0CFF83A4646F2434198F8000002F0DAF84037402EBFB2E7D19AF86D3043B10023F2
:400780008AF80930637A002BFCD000238AF80930182200210AA802F057FD694B4FF0FF320C9340230D930023236062602372236894F8AA002344197E02F026F894F8AA00B1
:4007C00001F0E4FF012194F8AA0001F0B7FF2368002BFCD000239946CAF80430DAF80C200127059202F0EAF8059AE3689B1AB4F86E2093420ED36FB1042195F8AA0002F01E
:400800000FF894F8AA0002F01BF80028F9D107468AF80800237A03F0FF08002B48D16A682B689A4202D1002FDCD141E063680AA80BEB831343440A93C8F140030B9300F00A
:4008400001FB0B9B09F10109C3F1400398440D9B5FFA88F8E3B93B4E0220FFF755FCA6F1400EBEE80F000FC6BEE80F000FC6BEE80F000FC69EE80F0086E80F00A6F1300193
:400880004022022000F024FF703E40230C960D93B8F13F0FCAD962682B4B01321340002BBEBF03F1FF3363F03F030133636099E70AA8267A00F01CFB0220FFF725FC0D9BDA
:4008C000F6B2402B07D0022040221E4900F000FF0220FFF719FC0D9B022033F040021DBFC3F1400292B21749114600F0F1FE0220FFF70AFCFFF76EFC36B11448FFF7B7FCC2
:400900000220FFF7DBFC06E0114B09A81B88ADF82430FFF7C1FC627A4946237A0D48FFF7A6FC78E20C48FFF7A2FCD4F86E6016F03F0615D003206CE293640040A081FF1F9E
:400940003892FF1F7892FF1F3F000080A43B0000EA3A0000BE3B0000D13B00009F81FF1F012001F001FE95F86C0001F0F7FD02F00BFCB94BB94F1A7842F004021A701A7888
:4009800042F001021A701A7802F0FE021A701A7802F0FE021A7002F0FBFB686EFFF7F4FC01214FF4804341F6FF72084601F0E2FD85F8AA0001F070FFB8550136402EF9D1C7
:4009C000DFF8B882002708F199039EB207930137402F14BF3A46002218F8010F22440523127E402101F08CFF314646F2475298F8000001F097FF4036402FB6B2E7D1DAF8D3
:400A00006E3000269B09182231464FF0FF3B0AA8CAF800600593CAF804B0B1468AF80860B04602F011FC0D973746012000F016FF069601F0C3FFB8F1000F0AD14EB901201B
:400A400000F0EEFD012804D14022854900F0B2FE06462268834B01321340002BBCBF03F1FF3363F03F036168B8BF01338B4200F09080069B3BB1237A002B40F09B806B7A7C
:400A8000002B40F097800B9BBBBBB8F1000F09D0754A7F2199540133402BFBD1724A0B930A922AE04EB3012000F0BAFD28BBDFF8B8E13F2E0EF1400CBCE80F00AEE80F0034
:400AC000BCE80F00AEE80F00BCE80F00AEE80F009CE80F008EE80F00AEF130030A9307F101070B9607DD059BBB4204D0012000F0B5FE464601E04FF001080B9BDBB1236893
:400B0000079A0AA802EB83120D9BC3F1400313440C9300F0FCF90D9B6BB92A68514B01321340002BBEBF03F1FF3363F03F030133236040230D93636801332AD12B683F2BD2
:400B400027D14FF0000BC5F804B001F041FD85F808B06B6895F8AA002B44197E01F054FE95F8AA0001F012FE012195F8AA0001F0E5FD85F809B0637A002BFCD04FF0000B53
:400B800001208AF809B001F02FFD584601F0ECFC01E0069B2BB1237A63B96B7A53B90123069363685B453FF444AF09F10109D5F804B03EE761682D482268FFF758FB01F043
:400BC0000DFD012001F0D0FC002001F00DFD042194F8AA0001F024FE94F8AA0001F030FE0028F9D19AF8AA0001F0BEFD9AF809309AF808200293012303920193CDF80080A9
:400C00004B463A4605991A48FFF731FBB8F1000F16D1059BBB420AD0012000F001FD01280646F6D10E49FFF7C2FA3F2803DC012000F028FE04E0304600F010FE0137E8E73B
:400C4000FFF7C8FA0B48FFF712FB237A0BB10220DFE0094B16E500BF97650040A081FF1F7892FF1F3F0000803892FF1FDB3B0000E63B0000163C0000EC3A00009F81FF1FE7
:400C800094F86C0001F06AFC606EFFF77DFB6648FFF7EDFA00236372637A002BFCD0012001F0A2FC00238AF80930637A002BFCD0002001F099FC5D48FFF7D9FA5C4B19E03C
:400CC000002084F85E00FFF75FFB5A4B12E094F8683023B195F869200AB985F86C2094F869201AB113B9012385F86C305248FFF707FB524B1B88ADF828300AA8FFF7CCFA13
:400D000089E0FFF7EBFA02F021F9002002F0C4F82A2601F0EFFF002001F092FF324600210AA802F091FA17238DF828308DF8296001F044FE002001F0EDFB002002F080F920
:400D4000C82001F0FDFD0DEB0600FFF75BFA0DF13E00FFF778FA01F031FE012002F070F9322001F0EDFD0DF12E00FFF74BFA0DF14200FFF768FA012001F0CCFB4FF49670C9
:400D800001F0DEFD01F01AFE0DF13600FFF73AFA0DF14A00FFF757FA002001F0BBFB4FF4967001F0CDFD01F009FE022002F048F9322001F0C5FD0DF13200FFF723FA0DF113
:400DC0004600FFF740FA012001F0A4FB4FF4967001F0B6FD01F0F2FD0DF13A00FFF712FA0DF14E00FFF72FFA002001F093FB4FF4967001F0A5FD01F0E1FD002002F020F9E5
:400E0000002384F85E3002F023F801F0F5FE74E70120FFF753FA032000F020FD0848FFF726FAFFF744BB00BF253C0000333C0000EE3A0000F03A0000F281FF1FF23A0000AC
:400E4000403C000070B5002401254268002A4ED0C368002B4BD00368013A591C01601B784260802B01D145752FE013F0800F467D0269017D12D036B1D20042F004020331E8
:400E8000026101754475026903F00103D20042F004021A43037D0261033315E046B13F2B06D9D20042F004020331026101754475026943EA0223427D036112B143F0C00320
:400EC0000361037D447508330375037D072BBCD90269083B22FA03F38268511C81601370C368013BC360037D083B0375ADE770BD07B5027D42B102AA002102F8011D0260E5
:400F000001224260FFF79EFF03B05DF804FBF0B5012100244368002B43D0C268002A40D0427D4AB183685A1C8260827D1A70C3684475013BC360EDE7027D072A0BD806687C
:400F40000769751C05603578013B45EA07250832056143600275027D0369A2F10805EB40DBB203F0C006802E07D0C02E0ED103F03F0383754175802308E0C3F3401363F0F5
:400F80007F03033A03F08103027502E00575002BC0D08268551C85601370C368013BC360B8E7F0BD2DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E84B
:400FC00003008033062283F8002283E80300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EAE3
:40100000450502F0B9F8013C05F003052ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F086
:401040000103137012F8013C062743F0030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0E5F8072D90
:40108000F5D15E485E4E002550F8041F05F1105303F1520221F0FF075333C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A0023BE
:4010C00013609360136193614F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A13781C
:4011000043F010031370464A464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4B4F
:40114000A2F5616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0D3
:40118000030388F800302F4B48221A702E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7076
:4011C00094E80F0007C52B80BDE8F081004800404C0F00480F010049A146004025420040224200400440004006400040A2430040A0430040453C0000E8460040FCFFFF475F
:40120000A000004800760040540F0048F846004020760040580F004828760040035001400C0F0048C0510040180F0048200F00482C0F0048380F004832510040440F0048E6
:40124000CF0100491D51004001590040235B0040585B004076580040B0430040F946004008B501F0C5FF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC9B
:40128000086005E001F0B4FF0C2303604FF0FF33184608BDCC80FF1FC893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7CE
:4012C000114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F090FFFFF79AF8FEE700BF01000000143E0000124A134B10B51A60124A134C1368134843F4007389
:4013000013600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E07F
:4013400014ED00E0000000000080FF1F61120000BC760040C080FF1F08ED00E0F8B501F013FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E3525D
:401380001F700B3203F8946C1378054603F07F031370002001F0EAFA2378404A03F0F90323701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B304625
:4013C0001A7802F07F021A701A7802F0BF021A7023783343237001F0C9FA2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0CAFE07211720AB
:4014000001F0FCFA2949172001F0EAFA0721182001F0F4FA2649182001F0E2FA0721152001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062045
:4014400001F0DCFA1D49062001F0CAFA0721084601F0D4FA1A49072001F0C2FA0721082001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C203A
:4014800001F0BCFABDE8F84010490C2001F0A8BAA5430040944300409D60004012600040F851004084600040ED92FF1F2B1D0000651B0000291D00005D1C0000891C00002C
:4014C000B91C0000F11C0000311D0000A51D0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A60E9
:401500004FF080721A604FF400121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B8E
:4015400003221A70802203F8202C012001F014FE0D4B04221A7010BD0893FF1F0E93FF1F0C93FF1F0D93FF1F0993FF1FF892FF1F0B93FF1F8093FF1F00E100E09E60004099
:401580009C600040286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BFB892FF1F0A4A002313700A4A13700A4A13700A4A7F
:4015C00013700A4A13700A4A13700A4A13700A4B03221A70802203F8202C70470E93FF1F0C93FF1F0D93FF1F0993FF1FF892FF1F0B93FF1F8093FF1F28600040014B187812
:40160000704700BF0D93FF1F044B1A7802F0FF001AB118780022C0B21A7070470C93FF1F024A0C2303FB0020407870471493FF1F431E072B0CD8074A064B00010344805CAD
:401640005B7800F00F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032DDF
:4016800018D8DFE805F002070C110021084601F01BF80DE00021084600F0FAFF08E00021084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034BF9
:4016C000185538BD1493FF1FE492FF1FED92FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BF08
:40170000D588DB7884BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F29
:40174000385D01F011F811232946FE2218F8040001F0D6F806F5C04278321FFA89F118F8040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F0020353
:401780007355735D03F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BF1493FF1FFC5F0040ED92FF1FE492FF1F706000402DE9F0470446154688460029C2
:4017C00040D0431E072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F05C
:401800007FF807F5C0411FFA88F27831305D01F089F8DFF84490315D1AF8040000F0F4FF01211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F825
:40184000093002E00D4600E000252846BDE8F087ED92FF1FE492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BF1493FF1FDA
:40188000FE5F0040431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B22A
:4018C000E85450680133013050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087009
:4019000030BD00BF8493FF1F8093FF1F00600040FC92FF1FF992FF1F0E93FF1F0A93FF1F8193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012004
:40194000137070470E93FF1F0A93FF1FF992FF1F8093FF1F8193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B1556828
:40198000215C013BC9B229705168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000408493FF1F006000400D
:4019C000FC92FF1F8193FF1F0A93FF1FF992FF1F064A06231370064A01201370054B80221A70054B00221A70704700BF0E93FF1FF992FF1F0A93FF1F8193FF1F054B9A6820
:401A00003AB19A68044910709A680988518000229A607047FC92FF1F8493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F08C
:401A40006003202B05D0402B06D043B900F012FC04E001F0A1FB01E000F046FD10B9034B03221A7008BD00BF28600040F992FF1F0060004008B5084A084B01201978138819
:401A80000B449BB21380064B00221A70FFF7B6FF044B03221A7008BD8493FF1F8093FF1F0E93FF1FF992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE8084082
:401AC000FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BF0E93FF1FF992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BF0E93FF1F47
:401B0000F992FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFF892FF1F086000400E93FF1FF992FF1FFD
:401B4000074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF70470E93FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF3F
:401B8000174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A084915461378207829
:401BC000DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BD28600040F992FF1F0A93FF1F8193FF1F29600040054A00231380054A916819B191680B709B
:401C000092685380704700BF8493FF1FFC92FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B29342E7
:401C400038BF0380FFF728FE012008BDFC92FF1F0E93FF1F0A93FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370479E
:401C80000B6000401493FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B6000401493FF1F0B4B04221A700F3BDD
:401CC00093F83230094B93F8242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B6000401493FF1F0B4B08221A700F3B93F84230094B93F854
:401D0000302002F00302012A1EBF93F8332082F0800283F83320012283F83120704700BF0B6000401493FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF025
:401D4000000C31B3CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425799D
:401D80002D0658BF84F801C090700133DBB24908D7E7F0BD9F6000401493FF1F70600040FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5B6
:401DC000B3422A46F7D11B782B70FCB12223237001AD03232846637000F08AFE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082A86
:401E0000C1700371417100F10400EAD108B070BD6F3C00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144BB
:401E4000B9F1000F07D098F8044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D109
:401E8000054BFF221A70BDE8F08300BF1493FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC0035869002070470893FF1FD03C0000E7
:401EC0002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF868
:401F00000420597891425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000FF4
:401F40000BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F844
:401F800008A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D28838
:401FC000C2F307224A71083394E7BDE8F88F00BF0D93FF1F1493FF1F0993FF1FFC5F004070600040FA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300C6
:40200000406908BD0D93FF1F00212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B13
:402040001A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C3680E
:4020800003EB02094B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE7811
:4020C000BAF1000F06F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C20083393427D
:4021000009D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8C9
:40214000F84FFFF767BEBDE8F88F00BF1493FF1FFA92FF1F8293FF1F0D93FF1F0B93FF1F1093FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F048
:40218000010191700021D170517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF006000401493FF1FFC5F004030B4194B1A7902F0D5
:4021C0007F02531E072B27D8164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D1A6
:402200008C7006E00D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF006000401493FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE7D
:4022400043681B798B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BD0D93FF1F00600040FA92FF1F8293FF1F38B58A4A8A4C13780021DBB2CB
:4022800021801806517840F18D800A2900F20581DFE811F05D00030103010301030103010B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B68F0
:4022C0006360122310E0CB78022B12D18878FFF7E5FD002800F0E180436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B2CD
:40230000854209D3664A91786A4AEE2908BF1346634A917881B106E0187801320028F1D018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD80F6
:402340001A78228018E0BDE8384000F025BF13F0030313D0022B40F0A0802380504B0C211B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280C0
:40238000514A11784F4AC9B2117053706260ACE7012323804D4BEFE70123238013794C4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB2F2
:4023C0005AE0937803F0FF0153B9404B1A7891425FD01970404B01201870FFF715FE58E0481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB247
:40240000D2B20A70364A3249D25CCB5C9A4240D0314B01221A70FFF753FD3AE003F00303012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012BCB
:402440002BD1214A137803F0FD0315E003F00303012B13D008D3022B1FD1114B9B78E3B9BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A7937
:402480005AB998781B791749DBB2CA5C22EA0002CA54BDE83840FFF79BBA002038BD00BF00600040FC92FF1F0893FF1FD03C0000343D0000BC3C0000A73D0000A093FF1F3B
:4024C0001493FF1FB992FF1F0B93FF1F0D93FF1FFA92FF1FF892FF1F0C93FF1F0993FF1F8293FF1F0F93FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A804B
:40250000FFF786BB0020704700600040FC92FF1F943C0000014B1870704700BF7F640040014B1878704700BF69640040014B1870704700BF78650040064A0123136002F65F
:4025400088321268E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF72640040014B1870704700BF7665004073B515461E460B4C052300221F
:40258000019200920A4601461846237000F064F932462946207800F01FF90221207800F009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A11702F
:4025C000A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BF78640040704738B505460078012428B1CF
:4026000000F062FD285D0134E4B2F8E738BD08B50D2000F059FDBDE808400A2000F054BDF7B516461F460B4C00230325019300930A4601462846257000F00EF93A46314621
:40264000207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F0F2F83A463146207800F0ADF8294609
:40268000207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207842
:4026C00003B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1FB0
:40270000024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD875
:40274000084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C2548E
:4027800000207047012070471070004017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C4900D0
:4027C00003F0F10301F00E011943815400207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC28070
:40280000002001207047000017289FBF034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF147000408B
:4028400017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA021302
:40288000DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F075FC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F0E0
:4028C00067FC204610BD00BFE480FF1F030610B5044611D400F058FC084AE300117803F1804303F5F04319705378147001335370BDE8104000F04CBC10BD00BFE480FF1F56
:4029000030B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC28000200120704700000A
:4029400038B50446084DB4F5004F05D9286800F013FCA4F50044F6E7034B58686043BDE8384000F009BC00BFEC80FF1F024B1B7A584300F001BC00BFEC80FF1F0E4B00F024
:4029800003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B012275
:4029C0001A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0087
:402A000000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A99
:402A40001360084B4FF400421C60C3F8E82010BDBC92FF1FBD2A000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034AB9
:402A8000136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BFBC92FF1F024B1868C0F30040704700BF10E000E038B5FFF7E9
:402AC000F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BFC092FF1F024B03EB8003586859607047BC92FF1F134B144A1B78DBB20360127843EA02236E
:402B0000114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF2E
:402B40000301004904010049EC460040020100490101004900010049050100490601004910B500F011FB204A044613780A2043F002031370137C43F00203137412F80A3C45
:402B800043F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CB9
:402BC000A3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D4BAAB4300400E5900402F5B004080E200E008B500F0C5FA0F4A79
:402C0000137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0ABBA00BF0859004074
:402C4000044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F9101065
:402C80000A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470A590040803C00008A93FF1F8C93FF1F9093FF1F08B5102000F0A6F926
:402CC00007210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BDB12D0000095900408893FF1F10B5054C23781BB9FFF7DCFFB5
:402D000001232370BDE81040FFF72ABFD892FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF79F
:402D400015FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF9093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B5E
:402D80001B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270478A93FF1F8C93FF1F8893FF1F7047000010B500F0E7F9214A04461378DE
:402DC0000A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F040023F
:402E000003F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0A9B900BFED
:402E4000AB43004006590040275B004080E200E008B500F099F90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F8BC
:402E80000A3C937903F0FD039371BDE8084000F07FB900BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E04223C0
:402EC00008380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470259004092
:402F00008A3C00009693FF1F9C93FF1F9493FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BDE5
:402F400009300000015900409893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BFD992FF1F044B1A7802F0FB021A701A7842F001021A70704700590040D2
:402F800010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF9493FF1FA05B004031
:402FC0000E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270479693FF1FD3
:403000009C93FF1F9893FF1F70470000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040014B1870704700BF76640040D7
:40304000014B1870704700BF7C64004073B515461E460B4C04230022019200920A46014618462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDE6
:40308000FC80FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF74650040014B1870704700BFDF
:4030C0007764004000000000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C4601258C
:40310000002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B7823407E
:4031400003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BFE7
:4031800001380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E013790020704751
:4031C000006000401493FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B577
:403200001E460E4C0025E41AA410A54204D056F8253098470135F8E700F0E2FD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BDEC3D0000EC3D000024
:40324000EC3D0000F43D000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF330293F9
:4032800005934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF77E
:4032C000CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BFB3
:403300001D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A38945
:403340004FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F08700009E
:403380002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB130233F
:4033C0008DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A080002373
:403400004FF0FF3204930793059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A07B0
:4034400044BF2B228DF8532022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB023201217E
:40348000F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB83
:4034C00001210123F5E703B1059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039B1D
:40350000073323F007030833039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB89BC
:403540005B0601D4099801E04FF0FF301DB0BDE8F08F00BFBB3D0000C13D0000C53D000000000000CD3200002DE9F04791461F460A698B6806469342B8BF1346C9F80030AD
:4035800091F843200C46DDF8208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD0013598
:4035C000E368D9F800209B1A9D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430291
:4036000039463046C0470130F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D00123224639462F
:403640003046C0470130D5D009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A3E
:4036800040F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F151
:4036C0004205111D1960136884F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E003
:4037000021681A6811F0800F02D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F4B
:4037400000F104051D6003D1550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F009
:40378000040020602BB9002D7DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013C34
:4037C00005F1FF35C5EB0E0323612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111D3A
:403800001960156800216268284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF093
:40384000FF3026E023692A4639463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B984219
:40388000B8BF184605E00B7804F1420584F842308AE705B0BDE8F0836F3C0000CC3D000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431E9F
:4038C0000A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B7E
:4039000002F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B681218C2
:40394000226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018E0
:40398000824201BF106852680918216062605C602846BDE8384000F098B838BDE092FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E015
:4039C00000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304696
:403A000000F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F8A0
:403A40000130EBD10C233360304600F03EF8002070BD00BFE092FF1FDC92FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F88542C5
:403A80000ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7DCFB431C02D1236803B178
:403AC0002B6038BDC493FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F021102130215027265706C79203078253032787F
:403B000000686F6D696E6700626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E6973686564207365656B00796573006E6F00647269766520E7
:403B4000303A20257320647269766520313A2025730057616974696E6720666F72205553422E2E2E0055534220726561647900636F6D6D616E64203078253032780066614C
:403B8000696C2025642B25642B2564203D3D2025642C206E6F74202564007061737365643D256400756E64657272756E206166746572202564207061636B65747300636F25
:403BC000756E743D256420693D256420643D256400636D645F777269746500646F6E6520256420256400703D25642063723D25642063773D256420663D256420773D2564FF
:403C000020696E6465783D256420756E64657272756E3D25640077726974652066696E69736865640073746172742065726173696E670073746F702065726173696E670092
:403C400069646C650000510040100040510040300000000140001000140140000800400140000A004C014000020050014020003031323334353637383941424344454600E9
:403C8000000100000004000000100001000000040000001028000000000104000100000000000000000157494E5553420000303030303100000000000000000012034D0080
:403CC0005300460054003100300030000100000001000000D83C000001000000A73D0000000000000000000001000000F03C000001000000793D000004000000123D000014
:403D0000000000000000000000000000103D0000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF00020304030904160346006C007500CE
:403D4000780045006E00670069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F006C006F0067006900650073000009022E0001010036
:403D800080320904000004FF00000107050102400000070582024000000705030340000A0705840340000A12010002FF0001080912006E0100020180014300232D302B20AF
:403DC00000686C4C00656667454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E46704759000000ED120000F8B500BFF8BC08BC9E467047E7
:403E000035000000183E0000C880FF1FA0000000601200000000000000000000C893FF1FFF000000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D46
:403E400000FA0000400000000090D003FF0000000000000000000000000000000000000000000000000000000000000000000000B93D0000000000000000000000000000B0
:403E80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081FF1F00000000000000000000000063
: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
:403F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081
:403F40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041
@@ -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
:0200000005AB4E
:02000000861365
:0200000490402A
:4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0
:400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
:400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040
:4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
:0200000490501A
:0C00000000012E16106900002E321614AC
: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 */
@@ -34,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)
@@ -151,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);
@@ -168,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;
}
@@ -319,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))
@@ -356,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));
@@ -403,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;
@@ -463,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();
@@ -570,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. */
@@ -609,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. */
@@ -701,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 */
@@ -721,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);
}
@@ -926,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;
@@ -939,23 +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);
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");
/* UART_PutString("GO\r"); */
for (;;)
{
CyWdtClear();
@@ -971,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?
------
@@ -110,8 +103,10 @@ at least, check the CRC so what data's there is probably good.
| [AES Superplus / No Problem](doc/disk-aeslanier.md) | 🦖 | | hard sectors! |
| [Durango F85](doc/disk-durangof85.md) | 🦖 | | 5.25" |
| [DVK MX](doc/disk-mx.md) | 🦖 | | Soviet PDP-11 clone |
| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 8-inch |
| [Zilog MCZ](doc/disk-zilogmcz.md) | 🦖 | | 8-inch _and_ hard sectors |
| [Micropolis](doc/disk-micropolis.md) | 🦄 | | Micropolis 100tpi drives |
| [TI DS990 FD1000](doc/disk-tids990.md) | 🦄 | 🦄 | 8" |
| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 8" |
| [Zilog MCZ](doc/disk-zilogmcz.md) | 🦖 | | 8" _and_ hard sectors |
{: .datatable }
### Notes

View File

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

View File

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

@@ -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;
@@ -28,8 +29,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

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

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

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

@@ -121,7 +121,7 @@ uint8_t decode_side(uint8_t side)
* bit 5) and also whether we're above track 0x3f (in bit 6).
*/
return !!(side & 0x40);
return !!(side & 0x20);
}
AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord()
@@ -200,9 +200,8 @@ std::set<unsigned> MacintoshDecoder::requiredSectors(Track& track) const
count = 8;
std::set<unsigned> sectors;
do
while (count--)
sectors.insert(count);
while (count--);
return sectors;
}

View File

@@ -0,0 +1,61 @@
#include "globals.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "micropolis.h"
#include "bytes.h"
#include "fmt/format.h"
/* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. */
static const FluxPattern SECTOR_SYNC_PATTERN(32, 0xaaaa5555);
AbstractDecoder::RecordType MicropolisDecoder::advanceToNextRecord()
{
_fmr->seekToIndexMark();
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher);
if (matcher == &SECTOR_SYNC_PATTERN) {
readRawBits(16);
return SECTOR_RECORD;
}
return UNKNOWN_RECORD;
}
/* Adds all bytes, with carry. */
static uint8_t checksum(const Bytes& bytes) {
ByteReader br(bytes);
uint16_t sum = 0;
while (!br.eof()) {
if (sum > 0xFF) {
sum -= 0x100 - 1;
}
sum += br.read_8();
}
/* The last carry is ignored */
return sum & 0xFF;
}
void MicropolisDecoder::decodeSectorRecord()
{
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16);
auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
ByteReader br(bytes);
br.read_8(); /* sync */
_sector->logicalTrack = br.read_8();
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = br.read_8();
if (_sector->logicalSector > 15)
return;
if (_sector->logicalTrack > 77)
return;
br.read(10); /* OS data or padding */
_sector->data = br.read(256);
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = checksum(bytes.slice(1, 2+266));
br.read(5); /* 4 byte ECC and ECC-present flag */
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}

View File

@@ -0,0 +1,18 @@
#ifndef ZILOGMCZ_H
#define ZILOGMCZ_H
#define MICROPOLIS_ENCODED_SECTOR_SIZE (1+2+266+6)
class Sector;
class Fluxmap;
class MicropolisDecoder : public AbstractDecoder
{
public:
virtual ~MicropolisDecoder() {}
RecordType advanceToNextRecord();
void decodeSectorRecord();
};
#endif

View File

@@ -37,7 +37,7 @@ AbstractDecoder::RecordType MxDecoder::advanceToNextRecord()
const FluxMatcher* matcher = nullptr;
_sector->clock = _clock = _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)
{

87
arch/tids990/decoder.cc Normal file
View File

@@ -0,0 +1,87 @@
#include "globals.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "tids990/tids990.h"
#include "crc.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "sector.h"
#include "record.h"
#include "track.h"
#include <string.h>
#include <fmt/format.h>
/* The Texas Instruments DS990 uses MFM with a scheme similar to a simplified
* version of the IBM record scheme (it's actually easier to parse than IBM).
* There are 26 sectors per track, each holding a rather weird 288 bytes.
*/
/*
* Sector record:
* data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 0 = 0x550a
* mfm: 00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 00 = 0x11112a44
* special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 00 = 0x11112244
* ^^
* When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data.
*/
const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244);
/*
* Data record:
* data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 1 = 0x550c
* mfm: 00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 01 = 0x11112a45
* special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 01 = 0x11112245
* ^^
* When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data.
*/
const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245);
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
AbstractDecoder::RecordType TiDs990Decoder::advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
}
void TiDs990Decoder::decodeSectorRecord()
{
auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16);
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE);
ByteReader br(bytes);
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3));
br.seek(2);
_sector->logicalSide = br.read_8() >> 3;
_sector->logicalTrack = br.read_8();
br.read_8(); /* number of sectors per track */
_sector->logicalSector = br.read_8();
br.read_be16(); /* sector size */
uint16_t wantChecksum = br.read_be16();
if (wantChecksum == gotChecksum)
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
}
void TiDs990Decoder::decodeDataRecord()
{
auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16);
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
ByteReader br(bytes);
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3));
br.seek(2);
_sector->data = br.read(TIDS990_PAYLOAD_SIZE);
uint16_t wantChecksum = br.read_be16();
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}

176
arch/tids990/encoder.cc Normal file
View File

@@ -0,0 +1,176 @@
#include "globals.h"
#include "record.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "tids990.h"
#include "crc.h"
#include "sectorset.h"
#include "writer.h"
#include <fmt/format.h>
FlagGroup tids990EncoderFlags;
static IntFlag trackLengthMs(
{ "--tids990-track-length-ms" },
"Length of a track in milliseconds.",
166);
static IntFlag sectorCount(
{ "--tids990-sector-count" },
"Number of sectors per track.",
26);
static IntFlag clockRateKhz(
{ "--tids990-clock-rate-khz" },
"Clock rate of data to write.",
500);
static HexIntFlag am1Byte(
{ "--tids990-am1-byte" },
"16-bit RAW bit pattern to use for the AM1 ID byte",
0x2244);
static HexIntFlag am2Byte(
{ "--tids990-am2-byte" },
"16-bit RAW bit pattern to use for the AM2 ID byte",
0x2245);
static IntFlag gap1(
{ "--tids990-gap1-bytes" },
"Size of gap 1 (the post-index gap).",
80);
static IntFlag gap2(
{ "--tids990-gap2-bytes" },
"Size of gap 2 (the post-ID gap).",
21);
static IntFlag gap3(
{ "--tids990-gap3-bytes" },
"Size of gap 3 (the post-data or format gap).",
51);
static StringFlag sectorSkew(
{ "--tids990-sector-skew" },
"Order to emit sectors.",
"1mhc72nid83oje94pkfa50lgb6");
static int charToInt(char c)
{
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
}
void TiDs990Encoder::writeRawBits(uint32_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i=0; i<width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void TiDs990Encoder::writeBytes(const Bytes& bytes)
{
encodeMfm(_bits, _cursor, bytes, _lastBit);
}
void TiDs990Encoder::writeBytes(int count, uint8_t byte)
{
Bytes bytes = { byte };
for (int i=0; i<count; i++)
writeBytes(bytes);
}
static uint8_t decodeUint16(uint16_t raw)
{
Bytes b;
ByteWriter bw(b);
bw.write_be16(raw);
return decodeFmMfm(b.toBits())[0];
}
std::unique_ptr<Fluxmap> TiDs990Encoder::encode(
int physicalTrack, int physicalSide, const SectorSet& allSectors)
{
double clockRateUs = 1e3 / clockRateKhz / 2.0;
int bitsPerRevolution = (trackLengthMs * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
uint8_t am1Unencoded = decodeUint16(am1Byte);
uint8_t am2Unencoded = decodeUint16(am2Byte);
writeBytes(gap1, 0x55);
bool first = true;
for (char sectorChar : sectorSkew.get())
{
int sectorId = charToInt(sectorChar);
if (!first)
writeBytes(gap3, 0x55);
first = false;
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
if (!sectorData)
Error() << fmt::format("format tried to find sector {} which wasn't in the input file", sectorId);
/* Writing the sector and data records are fantastically annoying.
* The CRC is calculated from the *very start* of the record, and
* include the malformed marker bytes. Our encoder doesn't know
* about this, of course, with the result that we have to construct
* the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
{
Bytes header;
ByteWriter bw(header);
writeBytes(12, 0x55);
bw.write_8(am1Unencoded);
bw.write_8(sectorData->logicalSide << 3);
bw.write_8(sectorData->logicalTrack);
bw.write_8(sectorCount);
bw.write_8(sectorData->logicalSector);
bw.write_be16(sectorData->data.size());
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
writeRawBits(am1Byte, 16);
writeBytes(header.slice(1));
}
writeBytes(gap2, 0x55);
{
Bytes data;
ByteWriter bw(data);
writeBytes(12, 0x55);
bw.write_8(am2Unencoded);
bw += sectorData->data;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
writeRawBits(am2Byte, 16);
writeBytes(data.slice(1));
}
}
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeBytes(1, 0x55);
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits, clockRateUs*1e3);
return fluxmap;
}

47
arch/tids990/tids990.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef TIDS990_H
#define TIDS990_H
#define TIDS990_PAYLOAD_SIZE 288 /* bytes */
#define TIDS990_SECTOR_RECORD_SIZE 10 /* bytes */
#define TIDS990_DATA_RECORD_SIZE (TIDS990_PAYLOAD_SIZE + 4) /* bytes */
class Sector;
class SectorSet;
class Fluxmap;
class Track;
class TiDs990Decoder : public AbstractDecoder
{
public:
virtual ~TiDs990Decoder() {}
RecordType advanceToNextRecord();
void decodeSectorRecord();
void decodeDataRecord();
};
class TiDs990Encoder : public AbstractEncoder
{
public:
virtual ~TiDs990Encoder() {}
private:
void writeRawBits(uint32_t data, int width);
void writeBytes(const Bytes& bytes);
void writeBytes(int count, uint8_t value);
void writeSync();
public:
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
private:
std::vector<bool> _bits;
unsigned _cursor;
bool _lastBit;
};
extern FlagGroup tids990EncoderFlags;
#endif

View File

@@ -261,8 +261,8 @@ You should now have a working board, so it's time to test it.
[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 900kB/s in both directions.
FluxEngine needs about 400kB/s for a DD disk and about 850kB/s for a HD
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

View File

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

55
doc/disk-micropolis.md Normal file
View File

@@ -0,0 +1,55 @@
Disk: Micropolis
================
Micropolis MetaFloppy disks use MFM and hard sectors. They were 100 TPI and
stored 315k per side. Each of the 16 sectors contains 266 bytes of "user data,"
allowing 10 bytes of metadata for use by the operating system. Micropolis DOS
(MDOS) used the metadata bytes, but CP/M did not.
Some later systems were Micropolis-compatible and so were also 100 TPI, like
the Vector Graphic Dual-Mode Disk Controller which was paired with a Tandon
drive.
**Important note:** You _cannot_ read these disks with a normal PC drive, as
these drives are 96tpi.The track spacing is determined by the physical geometry
of the drive and can't be changed in software. You'll need to get hold of a
100tpi Micropolis drive. Luckily these seem to use the same connector and
pinout as a 96tpi PC 5.25" drive. In use they should be identical.
Reading disks
-------------
Just do:
```
fluxengine read micropolis
```
You should end up with a `micropolis.img` which is 630784 bytes long (for a
normal DD disk). The image is written in CHS order, but HCS is generally used
by CP/M tools so the image needs to be post-processed. For only half-full disks
or single-sided disks, you can use `-s :s=0` to read only one side of the disk
which works around the problem.
The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK,
and SETSEC, but no function to select the head/side. Double-sided floppies
could be represented as having either twice the number of sectors, for CHS, or
twice the number of tracks, HCS; the second side's tracks logically followed
the first side (e.g., tracks 77-153). Micropolis disks tended to be the latter.
Useful references
-----------------
- [Micropolis 1040/1050 S-100 Floppy Disk Subsystems User's Manual][micropolis1040/1050].
Section 6, pages 261-266. Documents pre-ECC sector format. Note that the
entire record, starting at the sync byte, is controlled by software
- [Vector Graphic Dual-Mode Disk Controller Board Engineering Documentation][vectordualmode].
Section 1.6.2. Documents ECC sector format
- [AltairZ80 Simulator Usage Manual][altairz80]. Section 10.6. Documents ECC
sector format and VGI file format
[micropolis1040/1050]: http://www.bitsavers.org/pdf/micropolis/metafloppy/1084-01_1040_1050_Users_Manual_Apr79.pdf
[vectordualmode]: http://bitsavers.org/pdf/vectorGraphic/hardware/7200-1200-02-1_Dual-Mode_Disk_Controller_Board_Engineering_Documentation_Feb81.pdf
[altairz80]: http://www.bitsavers.org/simh.trailing-edge.com_201206/pdf/altairz80_doc.pdf

46
doc/disk-tids990.md Normal file
View File

@@ -0,0 +1,46 @@
Disk: TI DS990 FD1000
=====================
The Texas Instruments DS990 was a multiuser modular computing system from 1998,
based around the TMS-9900 processor (as used by the TI-99). It had an 8" floppy
drive module, the FD1000, which was a 77-track, 288-byte sector FM/MFM system
with 26 sectors per track. The encoding scheme was very similar to a simplified
version of the IBM scheme, but of course not compatible. A double-sided disk
would store a very satisfactory 1126kB of data; here's one at <a
href="https://www.old-computers.com/museum/computer.asp?st=1&c=1025">old-computers.com</a>:
<div style="text-align: center">
<a href="https://www.old-computers.com/museum/computer.asp?st=1&c=1025">
<img src="tids990.jpg" style="max-width: 60%" alt="A DS990 at old-computers.com"></a>
</div>
FluxEngine will read and write these (but only the DSDD MFM variant).
Reading discs
-------------
Just do:
```
fluxengine read tids990
```
You should end up with an `tids990.img` which is 1153152 bytes long.
Writing discs
-------------
Just do:
```
fluxengine write tids990 -i tids990.img
```
Useful references
-----------------
- [The FD1000 Depot Maintenance
Manual](http://www.bitsavers.org/pdf/ti/990/disk/2261885-9701_FD1000depotVo1_Jan81.pdf)

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

BIN
doc/tids990.jpg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -34,6 +34,21 @@ interchangeably: you can specify either at (very nearly) any time. A very
common workflow is to read a disk to a flux file, and then reread from the flux
file while changing the decoder options, to save disk wear. It's also much faster.
### Connecting it up
To use, simply plug your FluxEngine into your computer and run the client. If a
single device is plugged in, it will be automatically detected and used.
If _more_ than one device is plugged in, you need to specify which one to use
with the `--device` parameter, which takes the device serial number as a
parameter. You can find out the serial numbers by running the command without
the `--device` parameter, and if more than one device is attached they will be
listed. The serial number is also shown whenever a connection is made.
You _can_ work with more than one FluxEngine at the same time, using different
invocations of the client; but be careful of USB bandwidth. If the devices are
connected via the same hub, the bandwidth will be shared.
### Source and destination specifiers
When reading from or writing _flux_ (either from or to a real disk, or a flux
@@ -117,6 +132,10 @@ exact format varies according to the extension:
FluxEngine's D64 support is currently limited to write only. It will work
with up to 40 logical tracks.
- `.diskcopy`: a Macintosh DiskCopy 4.2 file. This is a special-purpose
format due to the weird layout of Mac GCR disks, but it can also support
720kB and 1440kB IBM disks (although there's no real benefit).
### High density disks
High density disks use a different magnetic medium to low and double density
@@ -177,7 +196,8 @@ directory.
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.
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 [in the index page](../README.md).

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

@@ -1,6 +1,8 @@
#ifndef BYTES_H
#define BYTES_H
#include <string.h>
class ByteReader;
class ByteWriter;
@@ -10,6 +12,7 @@ public:
Bytes();
Bytes(unsigned size);
Bytes(const uint8_t* ptr, size_t len);
Bytes(const std::string& data);
Bytes(std::initializer_list<uint8_t> data);
Bytes(std::shared_ptr<std::vector<uint8_t>> data);
Bytes(std::shared_ptr<std::vector<uint8_t>> data, unsigned start, unsigned end);
@@ -51,14 +54,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;
@@ -272,6 +274,16 @@ public:
ByteWriter& operator += (std::istream& stream);
ByteWriter& append(const char* data)
{
return *this += Bytes((const uint8_t*)data, strlen(data));
}
ByteWriter& append(const std::string& data)
{
return *this += Bytes(data);
}
ByteWriter& append(const Bytes data)
{
return *this += data;

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ void AbstractDecoder::decodeToSectors(Track& track)
return;
if ((r == UNKNOWN_RECORD) || (r == DATA_RECORD))
{
fmr.readNextMatchingOpcode(F_OP_PULSE);
fmr.findEvent(F_BIT_PULSE);
continue;
}
@@ -56,7 +56,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();

View File

@@ -30,16 +30,15 @@ static DoubleFlag minimumClockUs(
"Refuse to detect clocks shorter than this, to avoid false positives.",
0.75);
int FluxmapReader::readOpcode(unsigned& ticks)
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,21 +46,21 @@ 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;
}
}
@@ -73,7 +72,7 @@ unsigned FluxmapReader::readInterval(nanoseconds_t clock)
while (ticks < thresholdTicks)
{
unsigned thisTicks = readNextMatchingOpcode(F_OP_PULSE);
unsigned thisTicks = findEvent(F_BIT_PULSE);
if (!thisTicks)
break;
ticks += thisTicks;
@@ -196,7 +195,7 @@ void FluxmapReader::seek(nanoseconds_t ns)
while (!eof() && (_pos.ticks < ticks))
{
unsigned t;
readOpcode(t);
getNextEvent(t);
}
_pos.zeroes = 0;
}
@@ -237,7 +236,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,7 +247,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
void FluxmapReader::seekToIndexMark()
{
readNextMatchingOpcode(F_OP_INDEX);
findEvent(F_BIT_INDEX);
_pos.zeroes = 0;
}

View File

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

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

@@ -1,10 +1,12 @@
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "usb.h"
#include "usb/usb.h"
#include "fluxsink/fluxsink.h"
FlagGroup hardwareFluxSinkFlags;
FlagGroup hardwareFluxSinkFlags = {
&usbFlags,
};
static bool high_density = false;
@@ -36,8 +38,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

@@ -1,11 +1,13 @@
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "usb.h"
#include "usb/usb.h"
#include "fluxsource/fluxsource.h"
#include "fmt/format.h"
FlagGroup hardwareFluxSourceFlags;
FlagGroup hardwareFluxSourceFlags = {
&usbFlags
};
static DoubleFlag revolutions(
{ "--revolutions" },
@@ -50,9 +52,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

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

View File

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

View File

@@ -29,12 +29,15 @@ private:
const SectorSet& sectors, const ImageSpec& spec);
static std::unique_ptr<ImageWriter> createD64ImageWriter(
const SectorSet& sectors, const ImageSpec& spec);
static std::unique_ptr<ImageWriter> createDiskCopyImageWriter(
const SectorSet& sectors, const ImageSpec& spec);
static Constructor findConstructor(const ImageSpec& spec);
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"

View File

@@ -1,6 +1,6 @@
#include "globals.h"
#include "flags.h"
#include "usb.h"
#include "usb/usb.h"
#include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h"
#include "reader.h"
@@ -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);
@@ -245,13 +261,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 with {:.2f}us clock: status {}\n",
sector.logicalTrack, sector.logicalSide, sector.logicalSector,
sector.position.ns() / 1000.0, sector.clock / 1000.0,
sector.status);
hexdump(std::cout, sector.data);
std::cout << std::endl;
}
}
@@ -280,7 +296,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

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

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

View File

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

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

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

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

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

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

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

View File

@@ -1,6 +1,5 @@
#define _USE_MATH_DEFINES
#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

@@ -4,18 +4,18 @@
#include "writer.h"
#include "sql.h"
#include "protocol.h"
#include "usb.h"
#include "usb/usb.h"
#include "dataspec.h"
#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

@@ -154,6 +154,7 @@ buildlibrary libbackend.a \
lib/imagereader/imagereader.cc \
lib/imagereader/imgimagereader.cc \
lib/imagewriter/d64imagewriter.cc \
lib/imagewriter/diskcopyimagewriter.cc \
lib/imagewriter/imagewriter.cc \
lib/imagewriter/imgimagewriter.cc \
lib/imagewriter/ldbsimagewriter.cc \
@@ -170,11 +171,13 @@ buildlibrary libbackend.a \
arch/ibm/decoder.cc \
arch/ibm/encoder.cc \
arch/macintosh/decoder.cc \
arch/micropolis/decoder.cc \
arch/mx/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \
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 \
@@ -191,15 +194,15 @@ buildlibrary libbackend.a \
lib/fluxsource/kryoflux.cc \
lib/fluxsource/sqlitefluxsource.cc \
lib/fluxsource/streamfluxsource.cc \
lib/usb/usb.cc \
lib/usb/fluxengineusb.cc \
lib/globals.cc \
lib/hexdump.cc \
lib/image.cc \
lib/ldbs.cc \
lib/reader.cc \
lib/sector.cc \
lib/sectorset.cc \
lib/sql.cc \
lib/usb.cc \
lib/visualiser.cc \
lib/writer.cc \
@@ -222,7 +225,9 @@ buildlibrary libfrontend.a \
src/fe-readfb100.cc \
src/fe-readibm.cc \
src/fe-readmac.cc \
src/fe-readmicropolis.cc \
src/fe-readmx.cc \
src/fe-readtids990.cc \
src/fe-readvictor9k.cc \
src/fe-readzilogmcz.cc \
src/fe-rpm.cc \
@@ -234,6 +239,7 @@ buildlibrary libfrontend.a \
src/fe-writeamiga.cc \
src/fe-writebrother.cc \
src/fe-writeibm.cc \
src/fe-writetids990.cc \
src/fe-writeflux.cc \
src/fe-writetestpattern.cc \
src/fluxengine.cc \
@@ -263,7 +269,6 @@ buildsimpleprogram brother240tool \
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

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,7 +4,6 @@
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "decoders/decoders.h"
#include "image.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "record.h"
@@ -73,7 +72,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]++;
@@ -156,7 +155,11 @@ 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;
}
}
@@ -186,6 +189,12 @@ 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;
@@ -209,7 +218,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;

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,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 "macintosh/macintosh.h"
#include "sector.h"
#include "sectorset.h"
#include "image.h"
#include "record.h"
#include "fmt/format.h"
#include <fstream>

26
src/fe-readmicropolis.cc Normal file
View File

@@ -0,0 +1,26 @@
#include "globals.h"
#include "flags.h"
#include "reader.h"
#include "fluxmap.h"
#include "encoders/encoders.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "sectorset.h"
#include "record.h"
#include "micropolis/micropolis.h"
#include "fmt/format.h"
static FlagGroup flags { &readerFlags };
int mainReadMicropolis(int argc, const char* argv[])
{
setReaderDefaultSource(":t=0-76");
setReaderDefaultOutput("micropolis.img");
setReaderRevolutions(21); /* 17 index holes * 1.25 */
flags.parseFlags(argc, argv);
MicropolisDecoder decoder;
readDiskCommand(decoder);
return 0;
}

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"

27
src/fe-readtids990.cc Normal file
View File

@@ -0,0 +1,27 @@
#include "globals.h"
#include "flags.h"
#include "reader.h"
#include "fluxmap.h"
#include "encoders/encoders.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "sectorset.h"
#include "record.h"
#include "tids990/tids990.h"
#include "fmt/format.h"
static FlagGroup flags { &readerFlags };
int mainReadTiDs990(int argc, const char* argv[])
{
setReaderDefaultSource(":t=0-76");
setReaderDefaultOutput("tids990.img");
setReaderRevolutions(2);
flags.parseFlags(argc, argv);
TiDs990Decoder decoder;
readDiskCommand(decoder);
return 0;
}

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

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

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

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

View File

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

View File

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

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

View File

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

View File

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

23
src/fe-writetids990.cc Normal file
View File

@@ -0,0 +1,23 @@
#include "globals.h"
#include "flags.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "tids990/tids990.h"
#include "writer.h"
#include "fmt/format.h"
#include <fstream>
static FlagGroup flags { &writerFlags, &tids990EncoderFlags };
int mainWriteTiDs990(int argc, const char* argv[])
{
setWriterDefaultInput(":c=77:h=2:s=26:b=288");
setWriterDefaultDest(":d=0:t=0-76:s=0-1");
flags.parseFlags(argc, argv);
TiDs990Encoder encoder;
writeDiskCommand(encoder);
return 0;
}

View File

@@ -21,7 +21,9 @@ extern command_cb mainReadF85;
extern command_cb mainReadFB100;
extern command_cb mainReadIBM;
extern command_cb mainReadMac;
extern command_cb mainReadMicropolis;
extern command_cb mainReadMx;
extern command_cb mainReadTiDs990;
extern command_cb mainReadVictor9K;
extern command_cb mainReadZilogMCZ;
extern command_cb mainRpm;
@@ -32,6 +34,7 @@ extern command_cb mainUpgradeFluxFile;
extern command_cb mainWriteAmiga;
extern command_cb mainWriteBrother;
extern command_cb mainWriteIbm;
extern command_cb mainWriteTiDs990;
extern command_cb mainWriteFlux;
extern command_cb mainWriteTestPattern;
@@ -76,7 +79,9 @@ static std::vector<Command> readables =
{ "fb100", mainReadFB100, "Reads FB100 disks.", },
{ "ibm", mainReadIBM, "Reads the ubiquitous IBM format disks.", },
{ "mac", mainReadMac, "Reads Apple Macintosh disks.", },
{ "micropolis", mainReadMicropolis, "Reads Micropolis disks.", },
{ "mx", mainReadMx, "Reads MX disks.", },
{ "tids990", mainReadTiDs990, "Reads Texas Instruments DS990 disks.", },
{ "victor9k", mainReadVictor9K, "Reads Victor 9000 disks.", },
{ "zilogmcz", mainReadZilogMCZ, "Reads Zilog MCZ disks.", },
};
@@ -86,6 +91,7 @@ static std::vector<Command> writeables =
{ "amiga", mainWriteAmiga, "Writes Amiga disks.", },
{ "brother", mainWriteBrother, "Writes 120kB and 240kB Brother word processor disks.", },
{ "ibm", mainWriteIbm, "Writes the ubiquitous IBM format disks.", },
{ "tids990", mainWriteTiDs990, "Writes Texas Instruments DS990 disks.", },
};
static std::vector<Command> convertables =

View File

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

View File

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