Compare commits

...

77 Commits

Author SHA1 Message Date
David Given
8bffb38117 Fix bad merge. 2021-01-05 02:16:55 +01:00
David Given
eb5d545c35 Remember to unicornify the Mac formats. 2021-01-05 02:09:47 +01:00
David Given
a79a545730 Merge pull request #205 from davidgiven/writemac
Add Macintosh write support.
2021-01-05 02:08:52 +01:00
David Given
3863dab944 Try to fix an AppVeyor issue. 2021-01-05 01:56:49 +01:00
David Given
e53b7ecd8b Rebuild firmware. 2021-01-05 01:51:29 +01:00
David Given
7d88673ed5 Merge from trunk. 2021-01-05 01:36:54 +01:00
David Given
41f2da71e4 Merge. 2021-01-05 01:36:10 +01:00
David Given
cb4ee0fd74 Comment typo fix. 2021-01-05 01:36:00 +01:00
David Given
088381a5a6 Add a DiskCopy 4.2 image reader. We can now write files to floppy, and they
almost work.
2021-01-05 00:36:06 +01:00
David Given
629af2a697 You can now create Bytes from strings and vice versa. 2021-01-04 23:31:54 +01:00
David Given
884edfd497 Tweak the Mac encoder parameters to work. 2021-01-04 23:06:15 +01:00
David Given
83dd9e462e Fix sequencer bug where intervals of 0 would go horribly wrong. 2021-01-04 22:06:19 +01:00
David Given
70a6dfd98a Warning fix. 2021-01-04 20:16:36 +01:00
David Given
7f5d96382b Update PSoC components. 2021-01-04 20:16:15 +01:00
David Given
fd4d1c4bb7 Writing Mac disks now looks like it's about half working, but some of the
tracks are ending up mangled on disk --- maybe it's a firmware bug?
2021-01-04 19:16:09 +01:00
David Given
7eaf3de572 Mac encoder is code-complete. 2021-01-04 17:48:59 +01:00
David Given
4b608de3fb Merge pull request #186 from davidgiven/8inch
Add some utility features for 8" drives
2021-01-04 01:50:59 +01:00
David Given
b47e6e852b Merge pull request #188 from davidgiven/jv3
Add support for reading TRS-80 JV3 images.
2021-01-04 01:50:38 +01:00
David Given
a8a8ce4a36 Do the basic skeleton of the writer. 2021-01-04 01:49:15 +01:00
David Given
c61376d5a1 Add skeleton of Mac encoder. 2021-01-04 00:50:12 +01:00
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
6e2bdcad79 Document the JV3 format. 2020-09-18 00:56:02 +02:00
David Given
ef3c9f3d03 Add the fe-image command and the TRS-80 JV3 file reader. 2020-09-18 00:53:39 +02:00
David Given
5427f24df2 Merge from master. 2020-09-13 19:35:47 +02:00
David Given
b374340303 Try multiplexing SIDE1 onto DIR. 2020-09-10 22:07:47 +02:00
David Given
c78ed2c6ad Add the TK43 pin, which goes low when seeking to track 43 or above. 2020-09-10 21:48:30 +02: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
69 changed files with 2557 additions and 800 deletions

View File

@@ -1,6 +1,7 @@
version: '{branch}.{build}'
clone_depth: 1
skip_tags: true
image: Visual Studio 2019
environment:
MSYSTEM: MINGW32

View File

@@ -1,244 +1,244 @@
:400000000080002011000000990F0000990F0000064A08B5136843F020031360044B1A6803F53F5302331A6000F0D4FFE8460040FA46004010B5054C237833B9044B13B103
:400040000448AFF300800123237010BD6881FF1F0000000018380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF00000000F1
:400080006C81FF1F18380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1F6C
:400000000080002011000000CD0F0000CD0F0000064A08B5136843F020031360044B1A6803F53F5302331A6000F0EEFFE8460040FA46004010B5054C237833B9044B13B181
:400040000448AFF300800123237010BD6881FF1F0000000058380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF00000000B1
:400080006C81FF1F58380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1F2C
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080024B012200205A7293
:4001000002F00EB98881FF1F10B5C4B2204601F023F90128FAD110BD08B572B60F4B0F49DA680132DA601A690132C82A08BF00221A615A6918690132A72A08BF00224A6142
:400140005B69002B0CBF02230023002814BF184643F0010002F04CFE62B608BD8881FF1F38B50446C5B2284602F07CF8062002F099FA44F00200C0B202F074F8062002F065
:4001800091FA284602F06EF8BDE83840062002F073BA10B5642402F05FF830B90120FFF7DFFF013CF7D1204610BD012010BD70B5C4B2054620460E4601F0CEF8012805D09D
:4001C000204601F0E7F92846FFF79EFF204601F0CBF8314605460246204601F087F9204601F0BAF80028FAD1284670BD38B5044D0024285D013402F005FA402CF9D138BDFD
:40020000A081FF1F08B502F01FFC002002F028FC02F03AFC02F044FC80B208BD10B50446012002F037F8642002F026FAFFF7EAFF2080002002F02EF8642002F01DFAFFF70D
:40024000E1FF608010BD08B502F02AFD002002F033FD02F045FD02F04FFD80B208BD10B50446FFF796FF322002F006FAFFF7EBFF20800020FFF774FF322002F0FDF9FFF759
:40028000E2FF608010BD0FB400B593B014AB53F8042B402102A8019302F0A6FE02A802F046F802F050F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF737
:4002C00023FF62782146BDE81040042001F09CB82C38000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8583043B10020EF
:4003000001F0A4FF002002F037FD002384F8583010BD00BF8881FF1F38B5104D837895F85B2004469A4204D0FFF7E4FF002385F85E302368C5F859302279094B1A71A37855
:40034000002B14BF0220012002F016FDE07802F00DFD2079BDE8384002F044BD8881FF1FE181FF1F38B50D4C94F8585065B904F15900FFF7D1FF012001F068FF4FF47A7041
:4003800002F07AF984F85E50E3682366012384F85830BDE8384002F0ADB900BF8881FF1FF8B51E4C0646FFF7DDFF94F85E3003B15EB91B48FFF767FFFFF7EBFE012000235B
:4003C00084F85E00636602F06DF93246616E1548FFF759FF114D0027636E9E4216D001F03BFF00B16F66636E9E4205DD0020FFF7B7FE6B6E013305E005DA0120FFF7B0FE4F
:400400006B6E013B6B6602F075F9E5E7322002F033F9BDE8F8400448FFF735BF8881FF1F39380000403800005D3800002DE9F04F99B062B602F0C8F99949042002F0ECF93C
:40044000984801F015FF984802F0B8FC974801F049FF02F099FB02F06BFA002002F08CFC01F064FF0221002000F02CFF904C012001F0A4F8002384F85B30FFF773FFFFF7BE
:4004800088FE84F86800FFF735FF012384F85B30FFF768FFFFF77DFE84F86900FFF72AFF844B94F86800844994F869202546002A14BF0A461A46002808BF19467F48FFF7E3
:4004C000E2FE0321084602F0F5F8264602F012F994F8583043B12A6EEB689B1A41F28832934201D9FFF706FF00F024FF18B97448FFF7C9FE04E000F023FF0028F7D10BE0F5
:4005000000F018FF10B902F0F5F8F9E76D48FFF7BAFE032001F03EF8032000F01DFF0128D4D16948FFF7F8FE68490320FFF73FFE94F86A106648FFF7A6FE94F86A30023B2D
:40054000142B00F2A783DFE813F01500A5031E00A5032400A5034600A5036C00A503CF00A503AA01A503DE02A503FD02A5030403A5031E0303238DF820308DF821300D233B
:400580008DF82230FBE294F86C00FFF709FF514BF2E2FFF7E7FE00236372E068627A02F0FF0132B9EB681B1AB3F57A7FF6DD0B4608E03BB100227272F168627A12B9EB68D3
:4005C0005B1AFAE707228DF8202004228DF82120ADF82230D3E20220FFF796FD4FF000080DF1200A02F086F84FF480790027C9EB0803DA1907F80A200137402FF9D10220C4
:40060000FFF782FD3A465146022000F0FDFEB9F10109EBD108F10108B8F1400FE2D12E4B38E04FF0010A4FF000080DF1200B02F061F84FF0000959460120FFF7B8FD08EBC8
:40064000090300270493049B1BF807203B44DBB2934209D08DE80C0041463B464A461F48FFF711FE4FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400F92
:40068000D5D151461648FFF7FEFDBAF1000F00F00E81144B1B8807A8ADF81C3070E200BF19010000F900000091000000C50000008881FF1F6F3800006B380000723800003E
:4006C0008A3800009D380000E181FF1FF281FF1FA73800001C3800001E380000B6380000D23800002038000094F86C0001F0D2FD606EFFF755FE02F0E3FBB04BDFF8D882AE
:400700001A78002702F0FB021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F0D1FB0220FFF7EEFC012141F6FF734FF48042084602F024FB84F8AA0005
:4007400001F046FF08F807000137402FF8D1DFF88CA200270AF199091FFA89F80137402F14BF3A4600221AF8010F22440623127E402101F061FF424646F244519AF8000073
:4007800001F06CFF08F14008402F1FFA88F8E5D196F86D3033B100237372637A002BFCD00023737200234FF0FF32236062602372236894F8AA002344197E01F0C1FE94F883
:4007C000AA0001F07FFE012194F8AA0001F052FE2368002BFCD0002398467360D6F80CA0012701F087FFE368B4F86E20CAEB030393420DD367B1042195F8AA0001F0ACFED5
:4008000094F8AA0001F0B8FE0028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF770FC6968402209EB8111022000F0E9FD6A68614B01321340002B59
:40084000BEBF03F1FF3363F03F03013308F101086360C6E70220277AFFF756FC00221146022000F0D1FDFFB20220FFF74DFCFFF7BDFC37B15348FFF706FD0220FFF72AFDBA
:4008800006E0514B08A81B88ADF82030FFF710FD627A4146237A4D48FFF7F5FCFDE14C48FFF7F1FCD4F86E7017F03F0701D00320F1E1012001F0F4FC95F86C0001F0EAFC0B
:4008C00002F0FEFA434BDFF810811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F0EDFA686EFFF756FD01214FF4804341F6FF72084641
:4009000001F0D4FC85F8AA0001F062FE08F807000137402FF8D1DFF8C490002709F199031FFA83F804930137402F14BF3A46002219F8010F22440523127E402101F07CFECD
:40094000414646F24B4299F8000001F087FE08F14008402F1FFA88F8E5D100274FF0FF33376098467360BB46BA463B46D6F86E9037724FEA99192168114A01310A40002AD9
:40098000BCBF02F1FF3262F03F026068B8BF013282426BD02BB1227A002A76D16A7A002A73D12068049A059302EB8010BAF1000F16D040223F2102F0E3FA1CE094650040B8
:4009C0003F000080DC38000022380000F6380000093900009B640040A081FF1F9F81FF1F014601370120FFF7E2FBC7EB0903D3F1000A4AEB030A2168AB4A01310A40002A39
:400A0000BEBF02F1FF3262F03F02013222606268059B01322AD12A683F2A27D14FF00008C5F8048001F070FC85F808806B6895F8AA002B44197E01F083FD95F8AA0001F0AF
:400A400041FD012195F8AA0001F014FD85F80980637A002BFCD04FF00008012086F8098001F05EFC404601F01BFC00E023B1237A5BB96B7A4BB90123626842453FF47BAF9B
:400A80000BF1010BD5F8048075E701F043FC012001F006FC002001F043FC042194F8AA0001F05AFD94F8AA0001F066FD0028F9D196F8AA0001F0F4FC737A327A0293012363
:400AC00003920193CDF800A05B463A4649467748FFF7D9FBBAF1000F08D0FFF787FB237A63B17348FFF7CFFB0220D4E0B945F4D070490120FFF75BFB0137F7E76E48FFF779
:400B0000C2FB6E4B38E094F86C0001F0C3FB606EFFF746FC6A48FFF7B6FB00236372637A002BFCD0012001F0FBFB00237372637A002BFCD0002001F0F3FB6248FFF7A3FB34
:400B4000614B19E0002084F85E00FFF729FC5F4B12E094F8683023B195F869200AB985F86C2094F869201AB113B9012385F86C305748FFF7D1FB574B1B88ADF8203008A811
:400B8000FFF796FB89E0FFF7B5FB02F07BF8002002F01EF82A2701F049FF002001F0ECFE3A46002108A802F0EBF917238DF820308DF8217001F09EFD002001F047FB002042
:400BC00002F0DAF8C82001F057FD0DF12200FFF725FB0DF13600FFF742FB01F08BFD012002F0CAF8322001F047FD0DF12600FFF715FB0DF13A00FFF732FB012001F026FBDA
:400C00004FF4967001F038FD01F074FD0DF12E00FFF704FB0DF14200FFF721FB002001F015FB4FF4967001F027FD01F063FD022002F0A2F8322001F01FFD0DEB0700FFF79E
:400C4000EDFA0DF13E00FFF70AFB012001F0FEFA4FF4967001F010FD01F04CFD0DF13200FFF7DCFA0DF14600FFF7F9FA002001F0EDFA4FF4967001F0FFFC01F03BFD00202C
:400C800002F07AF8002384F85E3001F07DFF01F04FFE74E70120FFF71DFB032000F07AFC0D48FFF7F0FA11E43F00008013390000433900003892FF1F4D390000243800006A
:400CC00055390000633900002638000028380000F281FF1F2A380000703900002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F88E
:400D0000002283E80300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0B9F8013CD4
:400D400005F003052ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013CA5
:400D8000062743F0030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0E5F8072DF5D15E485E4E0025E4
:400DC00050F8041F05F1105303F14A0221F0FF074B33C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A0023136093601361936100
:400E00004F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A53
:400E4000464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F889
:400E8000100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B48
:400EC00048221A702E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7094E80F0007C52B8067
:400F0000BDE8F08100480040140C00480F010049A146004025420040224200400440004006400040A2430040A043004075390000E8460040FCFFFF479000004800760040A3
:400F40001C0C0048F846004020760040200C00482876004003500140D40B0048C0510040E00B0048E80B0048F40B0048000C0048325100400C0C0048CF0100491D5100405C
:400F800001590040235B0040585B004076580040B0430040F946004008B501F0C5FF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F0B4FF34
:400FC0000C2303604FF0FF33184608BDCC80FF1F8893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1A0A
:40100000A64202D041F8045BF9E701381033E5E701F090FFFFF70AFAFEE700BF01000000443B0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF49
:4010400054F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E014ED00E0000000007C
:401080000080FF1F990F0000BC760040C080FF1F08ED00E0F8B501F013FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C05
:4010C0001378054603F07F031370002001F0EAFA2378404A03F0F90323701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B30461A7802F07F021A7020
:401100001A7802F0BF021A7023783343237001F0C9FA2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0CAFE0721172001F0FCFA294917206C
:4011400001F0EAFA0721182001F0F4FA2649182001F0E2FA0721152001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062001F0DCFA1D49062045
:4011800001F0CAFA0721084601F0D4FA1A49072001F0C2FA0721082001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C2001F0BCFABDE8F840CC
:4011C00010490C2001F0A8BAA5430040944300409D60004012600040F851004084600040AD92FF1F631A00009D180000611A000095190000C1190000F1190000291A00005D
:40120000691A0000DD1A0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF435
:4012400000121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8F3
:40128000202C012001F014FE0D4B04221A7010BDC892FF1FCE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F00E100E09E6000409C60004028600040AA
:4012C0001260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF7892FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4AD8
:4013000013700A4A13700A4B03221A70802203F8202C7047CE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F28600040014B1878704700BFCD92FF1F54
:40134000044B1A7802F0FF001AB118780022C0B21A707047CC92FF1F024A0C2303FB002040787047D492FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA27
:401380000020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F00207EC
:4013C0000C110021084601F01BF80DE00021084600F0FAFF08E00021084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034B185538BDD492FF1F8B
:40140000A492FF1FAD92FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F5C4
:401440000075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F011F81123D6
:401480002946FE2218F8040001F0D6F806F5C04278321FFA89F118F8040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F002037355735D03F0FD034E
:4014C0007355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFD492FF1FFC5F0040AD92FF1FA492FF1F706000402DE9F047044615468846002940D0431E072B3FD817
:40150000FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F07FF807F5C0411FFA4B
:4015400088F27831305D01F089F8DFF84490315D1AF8040000F0F4FF01211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E027
:4015800000252846BDE8F087AD92FF1FA492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFD492FF1FFE5F0040431E072B7C
:4015C0009FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E854506801330130C4
:4016000050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF4493FF1F83
:401640004093FF1F00600040BC92FF1FB992FF1FCE92FF1FCA92FF1F4193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047CE92FF1F72
:40168000CA92FF1FB992FF1F4093FF1F4193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B2297018
:4016C0005168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404493FF1F00600040BC92FF1F4193FF1F7F
:40170000CA92FF1FB992FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFCE92FF1FB992FF1FCA92FF1F4193FF1F054B9A683AB19A680449107089
:401740009A680988518000229A607047BC92FF1F4493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B9B
:4017800006D043B900F012FC04E001F0A1FB01E000F046FD10B9034B03221A7008BD00BF28600040B992FF1F0060004008B5084A084B0120197813880B449BB21380064B8A
:4017C00000221A70FFF7B6FF044B03221A7008BD4493FF1F4093FF1FCE92FF1FB992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE808404C
:40180000FFF746BF0320FFF795FF034B03221A7008BD00BFCE92FF1FB992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFCE92FF1FB992FF1F08B50A4BCA
:401840001A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFB892FF1F08600040CE92FF1FB992FF1F074B1B78DBB2042B9B
:4018800005D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047CE92FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D5F2
:4018C00038BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378E7
:40190000DBB20B700B7803F00F0328788342F1D138BD38BD28600040B992FF1FCA92FF1F4193FF1F29600040054A00231380054A916819B191680B7092685380704700BFD2
:401940004493FF1FBC92FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FED7
:40198000012008BDBC92FF1FCE92FF1FCA92FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040D492FF1F8A
:4019C000094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040D492FF1F0B4B04221A700F3B93F83230094B93F885
:401A0000242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040D492FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012AB1
:401A40001EBF93F8332082F0800283F83320012283F83120704700BF0B600040D492FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5E5
:401A8000144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C08F
:401AC00090700133DBB24908D7E7F0BD9F600040D492FF1F70600040FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B7881
:401B00002B70FCB12223237001AD03232846637000F08AFE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F1C0
:401B40000400EAD108B070BD9F3900002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F879
:401B8000044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE84C
:401BC000F08300BFD492FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047C892FF1F003A00002DE9F84F424B1A7822
:401C0000002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED1AF
:401C40009A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF44
:401C80004FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F862
:401CC00000A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083336
:401D000094E7BDE8F88F00BFCD92FF1FD492FF1FC992FF1FFC5F004070600040BA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDCD92FF1F74
:401D400000212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187867
:401D800001250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D0E2
:401DC00093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F00306A5
:401E000007DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F8E5
:401E40000A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE898
:401E8000F88F00BFD492FF1FBA92FF1F4293FF1FCD92FF1FCB92FF1FD092FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D17031
:401EC000517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040D492FF1FFC5F004030B4194B1A7902F07F02531E072B27D81B
:401F0000164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E096
:401F4000012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040D492FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D297
:401F8000012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDCD92FF1F00600040BA92FF1F4293FF1F38B58A4A8A4C13780021DBB221801806517840F175
:401FC0008D800A2900F20581DFE811F05D00030103010301030103010B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B686360122310E0CB7841
:40200000022B12D18878FFF7E5FD002800F0E180436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B2854209D3664A91785E
:402040006A4AEE2908BF1346634A917881B106E0187801320028F1D018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD801A78228018E0BDE844
:40208000384000F025BF13F0030313D0022B40F0A0802380504B0C211B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280514A11784F4AC9B21C
:4020C000117053706260ACE7012323804D4BEFE70123238013794C4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB25AE0937803F0FF01B5
:4021000053B9404B1A7891425FD01970404B01201870FFF715FE58E0481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB2D2B20A70364A324948
:40214000D25CCB5C9A4240D0314B01221A70FFF753FD3AE003F00303012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0A2
:40218000FD0315E003F00303012B13D008D3022B1FD1114B9B78E3B9BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A795AB998781B791749C8
:4021C000DBB2CA5C22EA0002CA54BDE83840FFF79BBA002038BD00BF00600040BC92FF1FC892FF1F003A0000643A0000EC390000D73A00006093FF1FD492FF1F7992FF1F74
:40220000CB92FF1FCD92FF1FBA92FF1FB892FF1FCC92FF1FC992FF1F4293FF1FCF92FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A80FFF786BB0020704732
:4022400000600040BC92FF1FC4390000014B1870704700BF78650040014B1878704700BF68650040014B1870704700BF76640040064A0123136002F688321268E0211064A3
:40228000034A1170A2F540721360704780E100E000E400E0014B1870704700BF77640040014B1870704700BF7A64004073B515461E460B4C05230022019200920A460146C7
:4022C0001846237000F064F932462946207800F01FF90221207800F009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170A2F2023213607047BC
:4023000080E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BF74650040704738B505460078012428B100F062FD285D01347D
:40234000E4B2F8E738BD08B50D2000F059FDBDE808400A2000F054BDF7B516461F460B4C00230325019300930A4601462846257000F00EF93A463146207800F0C9F8022181
:40238000207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F0F2F83A463146207800F0ADF82946207800F097F8207889
:4023C00003B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207803B0F0BDE280FF1FD4
:4024000073B515461E460B4C0023019300930A4601461846237000F0BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1F024B1878C0F3801032
:40244000704700BF8F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B124
:4024800042F0200201E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C2540020704701207047D6
:4024C0001070004017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E015B
:402500001943815400207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000021
:4025400017289FBF034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B00015A
:40258000185C00F0070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C3080
:4025C000002003F00F03A370E07010BD012010BD10B500F075FC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F067FC204610BD00BF7F
:40260000E480FF1F030610B5044611D400F058FC084AE300117803F1804303F5F04319705378147001335370BDE8104000F04CBC10BD00BFE480FF1F30B504060CD411F499
:40264000704509D1C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F56C
:40268000004F05D9286800F013FCA4F50044F6E7034B58686043BDE8384000F009BC00BFEC80FF1F024B1B7A584300F001BC00BFEC80FF1F0E4B00F003001A78490102F04B
:4026C000FC02104318701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040C1
:40270000044B00F00F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F5C1
:402740006440C9B2017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF40042E1
:402780001C60C3F8E82010BD7C92FF1FF527000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F001031360AD
:4027C00008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF7C92FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054DC9
:40280000002455F8243003B198470134052CF8D138BD00BF8092FF1F024B03EB80035868596070477C92FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA83
:402840000243104A0360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049CB
:40288000EC460040020100490101004900010049050100490601004910B500F011FB204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C2C
:4028C000937943F00103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183BFA
:402900001A70094A137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D4BAAB4300400E5900402F5B004080E200E008B500F0C5FA0F4A137803F0FE03137032
:40294000A2F5AA521D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0ABBA00BF08590040044A137803F03F032B
:4029800043EA8010C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A80A9
:4029C00042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470A590040B03900004A93FF1F4C93FF1F5093FF1F08B5102000F0A6F907210420FFF79AFE2F
:402A000007490420FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BDE92A0000095900404893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040B0
:402A4000FFF72ABF9892FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF715FE024B1B782046F5
:402A800010BD00BF09590040034A044B1B881088181A00B2704700BF5093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107386
:402AC00000B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270474A93FF1F4C93FF1F4893FF1F7047000010B500F0E7F9214A044613780A2043F001031370B1
:402B0000137C43F00103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C85
:402B400002F0FC0203F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0A9B900BFAB4300400659004043
:402B8000275B004080E200E008B500F099F90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD0307
:402BC0009371BDE8084000F07FB900BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B2042892
:402C0000137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A80704702590040BA3900005693FF1F90
:402C40005C93FF1F5493FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BD412D0000015900402D
:402C80005893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BF9992FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F038
:402CC000010403D10028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5493FF1FA05B00400E4A13881BB223B150
:402D000011880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475693FF1F5C93FF1F5893FF1F53
:402D400070470000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040014B1870704700BF7F640040014B1870704700BFDD
:402D80007B65004073B515461E460B4C04230022019200920A46014618462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDFC80FF1F074A0223E3
:402DC000136002F688321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF7A650040014B1870704700BF79650040000000008E
:402E0000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C460125002600F041F8814653
:402E400051460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B78234003430B7058460430C4
:402E800000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BF01380046FCD170473A
:402EC000EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E013790020704700600040D492FF1FF3
:402F0000002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B51E460E4C0025E41ABD
:402F4000A410A54204D056F8253098470135F8E700F0DEFD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD1C3B00001C3B00001C3B0000243B0000BA
:402F800003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029305934FF6FF73009136
:402FC0000491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF7CBFF03B05DF804FB50
:403000006081FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BF1D4653050FD5294638
:4030400000F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A3894FF0FF3043F0400332
:40308000A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F08700002DE9F04F9DB003930D
:4030C0008B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB130238DF82A3037463C465C
:4031000014F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A08000234FF0FF320493079372
:40314000059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A0744BF2B228DF85320CC
:4031800022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB02320121F5E701B107923B78AF
:4031C0002E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB01210123F5E703B14A
:40320000059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039B073323F00703083323
:40324000039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB895B0601D4099801E059
:403280004FF0FF301DB0BDE8F08F00BFEB3A0000F13A0000F53A000000000000053000002DE9F04791461F460A698B6806469342B8BF1346C9F8003091F843200C46DDF858
:4032C000208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD00135E368D9F800209B1A7D
:403300009D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430239463046C047013017
:40334000F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D00123224639463046C0470130D5D0CC
:4033800009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520FC
:4033C000834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F14205111D1960136803
:4034000084F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E021681A6811F0800F93
:4034400002D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F00F104051D6003D15E
:40348000550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F0040020602BB9002D82
:4034C0007DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013C05F1FF35C5EB0E03A1
:4035000023612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111D196015680021626806
:40354000284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF0FF3026E023692A4606
:4035800039463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B78D0
:4035C00004F1420584F842308AE705B0BDE8F0839F390000FC3A000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431E0A44914204D011F847
:40360000014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B02F8014FF8E710BD48
:4036400038B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B681218226063600C6023E0C7
:40368000A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018824201BF10685268A1
:4036C0000918216062605C602846BDE8384000F098B838BDA092FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E000F082F8234B1C6872
:403700001A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304600F052F8206029468B
:40374000304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F80130EBD10C233360DD
:40378000304600F03EF8002070BD00BFA092FF1F9C92FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F885420ED929463846FFF7ED
:4037C0008BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7DCFB431C02D1236803B12B6038BD8493FF1F50
:403800007047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F021102130215027265706C792030782530327800686F6D696E6700B4
:40384000626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E6973686564207365656B00796573006E6F00647269766520303A20257320647214
:4038800069766520313A2025730057616974696E6720666F72205553422E2E2E0055534220726561647900636F6D6D616E6420307825303278006661696C2025642B2564F5
:4038C0002B2564203D3D2025642C206E6F74202564007061737365643D256400756E64657272756E206166746572202564207061636B65747300636F756E743D2564206974
:403900003D256420643D256400636D645F777269746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D256420756E64657272756E3D91
:40394000256400756E64657272756E2100737563636573730073746172742065726173696E670073746F702065726173696E670069646C65000051004010004051004030EB
:403980000000000140001000140140000800400140000A004C01400002005001402000303132333435363738394142434445460000010000000400000010000100000004D2
:4039C0000000001028000000000104000100000000000000000157494E5553420000303030303100000000000000000012034D0053004600540031003000300001000000DE
:403A000001000000083A000001000000D73A0000000000000000000001000000203A000001000000A93A000004000000423A0000000000000000000000000000403A0000F8
:403A4000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E00670069006E0065002A03E3
:403A800043006F0077006C00610072006B00200054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FF0000010705010240E2
:403AC0000000070582024000000705030340000A0705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C0065666745464700303132339A
:403B000034353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000025100000F8B500BFF8BC08BC9E46704735000000483B0000C880FF1FA00000009F
:403B40002012000000000000000000008893FF1FFF000000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003FF0000007E
:403B80000000000000000000000000000000000000000000000000000000000000000000E93A000000000000000000000000000000000000000000000000000000000000E2
:403BC00000000000000000000000000000000000000000000000000000000000000000000081FF1F0000000000000000000000000000000000000000000000000000000026
:403C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084
:4001000002F028B98881FF1F10B5C4B2204601F03DF90128FAD110BD08B572B60F4B0F49DA680132DA601A690132C82A08BF00221A615A6918690132A72A08BF00224A610E
:400140005B69002B0CBF02230023002814BF184643F0010002F066FE62B608BD8881FF1F38B50446C5B2284602F096F8062002F0B3FA44F00200C0B202F08EF8062002F0FD
:40018000ABFA284602F088F8BDE83840062002F08DBA10B5642402F079F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F0E9F8012805D02046DB
:4001C00001F002FA2846FFF79FFF204601F0E6F8314605460246204601F0A2F9204601F0D5F80028FAD1284670BD000038B5044D0024285D013402F01FFA402CF9D138BDDB
:40020000A081FF1F08B502F039FC002002F042FC02F054FC02F05EFC80B208BD10B50446012002F051F8642002F040FAFFF7EAFF2080002002F048F8642002F037FAFFF73D
:40024000E1FF608010BD08B502F044FD002002F04DFD02F05FFD02F069FD80B208BD10B50446FFF796FF322002F020FAFFF7EBFF20800120FFF774FF322002F017FAFFF7BB
:40028000E2FF608010BD0FB400B593B014AB53F8042B402102A8019302F0C6FE02A802F060F802F06AF813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF7E3
:4002C00023FF62782146BDE81040042001F0B6B86C38000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8583043B1002095
:4003000001F0BEFF002002F051FD002384F8583010BD00BF8881FF1F38B5104D837895F85B2004469A4204D0FFF7E4FF002385F85E302368C5F859302279094B1A71A37821
:40034000002B14BF0220012002F030FDE07802F027FD2079BDE8384002F05EBD8881FF1FE181FF1F38B50D4C94F8585065B904F15900FFF7D1FF012001F082FF4FF47A70D9
:4003800002F094F984F85E50E3682366012384F85830BDE8384002F0C7B900BF8881FF1FF8B5214C0546FFF7DDFF94F85E3003B15DB91E48FFF767FFFFF7EBFE0120002323
:4003C00084F85E00636602F087F92A46616E1848FFF759FF144E0027636E9D4216D001F055FF00B17766636E9D4205DD0120FFF7B7FE736E013305E005DA0020FFF7B0FE0E
:40040000736E013B736602F08FF9E5E7322002F04DF92A2DCCBF0020012002F009FDBDE8F8400448FFF72FBF8881FF1F79380000803800009D3800002DE9F04F99B062B6B3
:4004400002F0DCF99949042002F000FA984801F029FF984802F0CCFC974801F05DFF02F0ADFB02F07FFA002002F0A0FC01F078FF0221002000F040FF904C012001F0B8F8D9
:40048000002384F85B30FFF76DFFFFF782FE84F86800FFF72FFF012384F85B30FFF762FFFFF777FE84F86900FFF724FF844B94F86800844994F869202546002A14BF0A46EA
:4004C0001A46002808BF19467F48FFF7DCFE0321084602F009F9264602F026F994F8583043B12A6EEB689B1A41F28832934201D9FFF700FF00F038FF18B97448FFF7C3FE30
:4005000004E000F037FF0028F7D10BE000F02CFF10B902F009F9F9E76D48FFF7B4FE032001F052F8032000F031FF0128D4D16948FFF7F2FE68490320FFF738FE94F86A10B5
:400540006648FFF7A0FE94F86A30023B142B00F2BA83DFE813F01500B8031E00B8032400B8034600B8036C00B803CF00B803B101B803F102B8031003B8031703B803310372
:4005800003238DF820308DF821300E238DF822300EE394F86C00FFF703FF514B05E3FFF7E1FE00236372E068627A02F0FF0132B9EB681B1AB3F57A7FF6DD0B4608E03BB10E
:4005C00000227272F168627A12B9EB685B1AFAE707228DF8202004228DF82120ADF82230E6E20220FFF790FD4FF000080DF1200A02F09AF84FF480790027C9EB0803DA1906
:4006000007F80A200137402FF9D10220FFF77CFD3A465146022000F011FFB9F10109EBD108F10108B8F1400FE2D12E4B38E04FF0010A4FF000080DF1200B02F075F84FF050
:40064000000959460120FFF7B1FD08EB090300270493049B1BF807203B44DBB2934209D08DE80C0041463B464A461F48FFF70BFE4FF0000A0137402FEBD109F10109B9F5E0
:40068000807FDED108F10108B8F1400FD5D151461648FFF7F8FDBAF1000F00F01581144B1B8807A8ADF81C3083E200BF19010000F900000091000000C50000008881FF1F76
:4006C000AF380000AB380000B2380000CA380000DD380000E181FF1FF281FF1FE73800005C3800005E380000F63800001239000060380000606EFFF753FE94F86C0001F0C6
:40070000E3FD94F86C0001F0C7FD02F0F9FBB64BDFF8F0821A78002702F0FB021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F0E7FB0220FFF7E4FC91
:40074000012141F6FF734FF48042084602F034FB84F8AA0001F056FF08F807000137402FF8D1DFF8A4A200270AF199091FFA89F80137402F14BF3A4600221AF8010F224438
:400780000623127E402101F071FF424646F24E419AF8000001F07CFF08F14008402F1FFA88F8E5D196F86D3033B100237372637A002BFCD00023737200234FF0FF32236069
:4007C00062602372236894F8AA002344197E01F0D1FE94F8AA0001F08FFE012194F8AA0001F062FE2368002BFCD0002398467360D6F80CA0012701F097FFE368B4F86E20C2
:40080000CAEB030393420DD367B1042195F8AA0001F0BCFE94F8AA0001F0C8FE0028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF766FC6968402271
:4008400009EB8111022000F0F9FD6A68674B01321340002BBEBF03F1FF3363F03F03013308F101086360C6E70220277AFFF74CFC00221146022000F0E1FD0220FFF744FC15
:40088000FFB2FFF7B3FC002001F006FD37B15848FFF7F9FC0220FFF71DFD06E0554B08A81B88ADF82030FFF703FD627A4146237A5148FFF7E8FC09E25048FFF7E4FCD4F8CD
:4008C0006E7017F03F0701D00320FDE1686EFFF767FD95F86C0001F0F7FC95F86C0001F0DBFC012001F0F6FC02F00AFB444BDFF814811A7842F004021A701A7842F001021C
:400900001A701A7802F0FE021A701A7802F0FE021A7002F0F9FA01214FF4804341F6FF72084601F0DDFC85F8AA0001F06BFE08F807000137402FF8D1DFF8CC90002709F1F8
:4009400099031FFA83F804930137402F14BF3A46002219F8010F22440523127E402101F085FE414646F24B5299F8000001F090FE08F14008402F1FFA88F8E5D100274FF0AF
:40098000FF33376098467360BB463B46D6F86E9037725FEA99190CBF4FF0010A4FF0000A2168124A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426CD02BB162
:4009C000227A002A77D16A7A002A74D12068049A059302EB8010BAF1000F17D040223F2102F0EEFA1DE000BF9E6400403F0000801C39000062380000363900004939000081
:400A00009B650040A081FF1F9F81FF1F014601370120FFF7CBFBC7EB0903D3F1000A4AEB030A2168AD4A01310A40002ABEBF02F1FF3262F03F02013222606268059B013223
:400A40002AD12A683F2A27D14FF00008C5F8048001F074FC85F808806B6895F8AA002B44197E01F087FD95F8AA0001F045FD012195F8AA0001F018FD85F80980637A002B3E
:400A8000FCD04FF00008012086F8098001F062FC404601F01FFC00E023B1237A5BB96B7A4BB90123626842453FF47AAF0BF1010BD5F8048074E701F047FC012001F00AFCFB
:400AC000002001F047FC042194F8AA0001F05EFD94F8AA0001F06AFD0028F9D196F8AA0001F0F8FC737A327A0293012303920193CDF800A05B463A4649467948FFF7C3FB59
:400B0000BAF1000F0BD0FFF771FB002001F0C4FB237A63B17348FFF7B6FB0220D4E0B945F1D071490120FFF741FB0137F7E76F48FFF7A9FB6E4B38E094F86C0001F0C4FB59
:400B4000606EFFF72DFC6B48FFF79DFB00236372637A002BFCD0012001F0FCFB00237372637A002BFCD0002001F0F4FB6248FFF78AFB624B19E0002084F85E00FFF710FCCF
:400B80005F4B12E094F8683023B195F869200AB985F86C2094F869201AB113B9012385F86C305848FFF7B8FB574B1B88ADF8203008A8FFF77DFB89E0FFF79CFB02F07CF8D9
:400BC000002002F01FF82A2701F04AFF002001F0EDFE3A46002108A802F0F2F917238DF820308DF8217001F09FFD002001F048FB002002F0DBF8C82001F058FD0DF12200DE
:400C0000FFF70CFB0DF13600FFF729FB01F08CFD012002F0CBF8322001F048FD0DF12600FFF7FCFA0DF13A00FFF719FB012001F027FB4FF4967001F039FD01F075FD0DF13A
:400C40002E00FFF7EBFA0DF14200FFF708FB002001F016FB4FF4967001F028FD01F064FD022002F0A3F8322001F020FD0DEB0700FFF7D4FA0DF13E00FFF7F1FA012001F05E
:400C8000FFFA4FF4967001F011FD01F04DFD0DF13200FFF7C3FA0DF14600FFF7E0FA002001F0EEFA4FF4967001F000FD01F03CFD002002F07BF8002384F85E3001F07EFF47
:400CC00001F050FE74E70120FFF704FB032000F07BFC0E48FFF7D7FAFFF7FEBB3F00008053390000833900003892FF1F8D3900006438000095390000A33900006638000055
:400D000068380000F281FF1F6A380000B03900002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F8002283E80300522203F5807326
:400D40001A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0BFF8013C05F003052ED0032DF0D1744BD2
:400D80004FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013C062743F0030302F8013C2378D8
:400DC000012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0EBF8072DF5D15E485E4E002550F8041F05F1105303F14802D4
:400E000021F0FF074933C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A002313609360136193614F4B504F1A68504BDFF888818F
:400E40001A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A464B07CA03C31A80454A28339D
:400E8000106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F8100EC2F8141E1378042043F009
:400EC00008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B48221A702E4A402313702E492B
:400F0000937013729372082382F81F3220220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7094E80F0007C52B80BDE8F081004800405C090048A4
:400F40000F010049A146004025420040224200400440004006400040A2430040A0430040B5390000E8460040FCFFFF478C0000480076004064090048F84600402076004024
:400F80006809004828760040035001401C090048C051004028090048300900483C090048480900483251004054090048CF0100491D51004001590040235B0040585B0040AE
:400FC00076580040B0430040F946004008B501F0CBFF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F0BAFF0C2303604FF0FF33184608BD0D
:40100000CC80FF1F8893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1AA64202D041F8045BF9E7013884
:401040001033E5E701F096FFFFF7F6F9FEE700BF01000000843B0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF54F823204FEA830188BF0E4A58
:401080000133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E014ED00E0000000000080FF1FCD0F0000BC7600403B
:4010C000C080FF1F08ED00E0F8B501F019FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C1378054603F07F031370002089
:4011000001F0EAFA2378404A03F0F90323701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B30461A7802F07F021A701A7802F0BF021A7023783343ED
:40114000237001F0C9FA2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0D0FE0721172001F0FCFA2949172001F0EAFA0721182001F0F4FAF2
:401180002649182001F0E2FA0721152001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062001F0DCFA1D49062001F0CAFA0721084601F0D4FA2F
:4011C0001A49072001F0C2FA0721082001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C2001F0BCFABDE8F84010490C2001F0A8BAA543004076
:40120000944300409D60004012600040F851004084600040AD92FF1F971A0000D1180000951A0000C9190000F5190000251A00005D1A00009D1A0000111B0000214B224AF4
:4012400010B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF400121A6020221A6018608020E7
:4012800018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8202C012001F01AFE0D4B04221F
:4012C0001A7010BDC892FF1FCE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F00E100E09E6000409C600040286000401260004070B5074C0546237848
:401300000E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF7892FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4A13700A4A13700A4B03221A7049
:40134000802203F8202C7047CE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F28600040014B1878704700BFCD92FF1F044B1A7802F0FF001AB1187845
:401380000022C0B21A707047CC92FF1F024A0C2303FB002040787047D492FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA0020023880B2704700207047FA
:4013C000FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F002070C110021084601F01BF80DE049
:401400000021084600F0FAFF08E00021084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034B185538BDD492FF1FA492FF1FAD92FF1F431E072B83
:401440002DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F50075ADB2254A43EA1523060119
:40148000B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F011F811232946FE2218F8040001F0D6F8E3
:4014C00006F5C04278321FFA89F118F8040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F002037355735D03F0FD037355BDE8F08703FB047463793A
:40150000DBB28AF80230BDE8F08700BFD492FF1FFC5F0040AD92FF1FA492FF1F706000402DE9F047044615468846002940D0431E072B3FD8FFF732FFA84203D22046FFF7CA
:401540002DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F07FF807F5C0411FFA88F27831305D01F089F8DFF854
:401580004490315D1AF8040000F0F4FF01211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E000252846BDE8F087AD92FF1FD4
:4015C000A492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFD492FF1FFE5F0040431E072B9FBF024B000108221A5470474D
:40160000FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E85450680133013050601088013880B21080ECE768
:4016400018460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF4493FF1F4093FF1F00600040BC92FF1F5C
:40168000B992FF1FCE92FF1FCA92FF1F4193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047CE92FF1FCA92FF1FB992FF1F4093FF1F5B
:4016C0004193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B229705168DBB20131516011880130B9
:40170000013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404493FF1F00600040BC92FF1F4193FF1FCA92FF1FB992FF1F064A0623D5
:401740001370064A01201370054B80221A70054B00221A70704700BFCE92FF1FB992FF1FCA92FF1F4193FF1F054B9A683AB19A68044910709A680988518000229A6070476E
:40178000BC92FF1F4493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B06D043B900F012FC04E001F0ED
:4017C000A7FB01E000F046FD10B9034B03221A7008BD00BF28600040B992FF1F0060004008B5084A084B0120197813880B449BB21380064B00221A70FFF7B6FF044B03221E
:401800001A7008BD4493FF1F4093FF1FCE92FF1FB992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE80840FFF746BF0320FFF795FF034BE0
:4018400003221A7008BD00BFCE92FF1FB992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFCE92FF1FB992FF1F08B50A4B1A7832B11A78094942F0800273
:401880000A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFB892FF1F08600040CE92FF1FB992FF1F074B1B78DBB2042B05D0062B05D0022B05D1FFF794
:4018C000A1BEFFF7C5BFFFF7D3BF7047CE92FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D538BD5A0602D5FFF7D7FF03E0AB
:401900009D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378DBB20B700B7803F00F03287851
:401940008342F1D138BD38BD28600040B992FF1FCA92FF1F4193FF1F29600040054A00231380054A916819B191680B7092685380704700BF4493FF1FBC92FF1F0E4808B54E
:4019800003889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FE012008BDBC92FF1FCE92FF1F3B
:4019C000CA92FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040D492FF1F094B02221A700F3B93F82230F1
:401A0000074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040D492FF1F0B4B04221A700F3B93F83230094B93F8242002F00302012A1EBF93F89F
:401A4000272082F0800283F82720012283F82520704700BF0B600040D492FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012A1EBF93F8332082F0800283F815
:401A80003320012283F83120704700BF0B600040D492FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5144A4FEA031E7244947850788D
:401AC0002040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C090700133DBB24908D7E7F0BD14
:401B00009F600040D492FF1F70600040FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B782B70FCB12223237001AD0323C9
:401B40002846637000F08AFE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F10400EAD108B070BDDF390000B8
:401B80002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F8044024064CBF887081F802C009
:401BC00001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE8F08300BFD492FF1F70600040F2
:401C0000FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047C892FF1F403A00002DE9F84F424B1A78002A7ED01878414D0138C0B226
:401C4000FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED19A7893F8039002F07F0206FB0C
:401C800002FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF4FF007094FF00D0981F80590F6
:401CC00081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F800A0B2F808A05FFA8AFA81F88C
:401D000001A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083394E7BDE8F88F00BFCD92FF1F5A
:401D4000D492FF1FC992FF1FFC5F004070600040BA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDCD92FF1F00212DE9F84F0B464E4E0C2779
:401D800007FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187801250138C0B2FFF725FFA846EC
:401DC0004368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D093F802A00AF07F06AE4229D1E5
:401E00000E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F0030607DA012E0CBF07260D264E7100
:401E400081F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F80A5C01F07F0100FB01418D728C
:401E8000F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE8F88F00BFD492FF1FBA92FF1F37
:401EC0004293FF1FCD92FF1FCB92FF1FD092FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D170517841F002015170127912F0DA
:401F0000800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040D492FF1FFC5F004030B4194B1A7902F07F02531E072B27D8164B0C2404FB02339978154DED
:401F400001F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E0012B0CBF082309238B7030BC59
:401F8000FFF7DCBB002030BC704700BF00600040D492FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D2012909D8074A0848535CDBB2A4
:401FC0004354A3780120DBB2535410BD002010BDCD92FF1F00600040BA92FF1F4293FF1F38B58A4A8A4C13780021DBB221801806517840F18D800A2900F20581DFE811F09D
:402000005D00030103010301030103010B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B686360122310E0CB78022B12D18878FFF7E5FD002870
:4020400000F0E180436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B2854209D3664A91786A4AEE2908BF1346634A91788D
:4020800081B106E0187801320028F1D018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD801A78228018E0BDE8384000F02BBF13F0030313D067
:4020C000022B40F0A0802380504B0C211B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280514A11784F4AC9B2117053706260ACE701232380B4
:402100004D4BEFE70123238013794C4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB25AE0937803F0FF0153B9404B1A7891425FD0197020
:40214000404B01201870FFF715FE58E0481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB2D2B20A70364A3249D25CCB5C9A4240D0314B0122DC
:402180001A70FFF753FD3AE003F00303012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0FD0315E003F00303012B13D045
:4021C00008D3022B1FD1114B9B78E3B9BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A795AB998781B791749DBB2CA5C22EA0002CA54BDE801
:402200003840FFF79BBA002038BD00BF00600040BC92FF1FC892FF1F403A0000A43A00002C3A0000173B00006093FF1FD492FF1F7992FF1FCB92FF1FCD92FF1FBA92FF1F53
:40224000B892FF1FCC92FF1FC992FF1F4293FF1FCF92FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A80FFF786BB0020704700600040BC92FF1F043A00000A
:40228000014B1870704700BF7A640040014B1878704700BF6B650040014B1870704700BF79640040064A0123136002F688321268E0211064034A1170A2F540721360704724
:4022C00080E100E000E400E0014B1870704700BF7A650040014B1870704700BF7865004073B515461E460B4C05230022019200920A4601461846237000F064F932462946A0
:40230000207800F01FF90221207800F009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B042227
:402340001A60704700E100E0014B04221A60704780E100E0014B1870704700BF7E640040704738B505460078012428B100F068FD285D0134E4B2F8E738BD08B50D2000F063
:402380005FFDBDE808400A2000F05ABDF7B516461F460B4C00230325019300930A4601462846257000F00EF93A463146207800F0C9F80221207800F0B3F8207803B0F0BD4E
:4023C000E080FF1FF7B516461F460B4C00230225019300930A4601462846257000F0F2F83A463146207800F0ADF82946207800F097F8207803B0F0BDE180FF1FF7B516468D
:402400001F460B4C00230125019300930A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207803B0F0BDE280FF1F73B515461E460B4C0023019385
:4024400000930A4601461846237000F0BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1F024B1878C0F38010704700BF8F450040074A7F236A
:40248000802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200201E002F0DF02C25443
:4024C000C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004017280DD8074900017F
:402500000B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E011943815400207047012070476F
:402540001070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF034B00011954002048
:4025800088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F0070088BFFF2070470B
:4025C00014700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD73
:40260000012010BD10B500F07BFC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F06DFC204610BD00BFE480FF1F030610B5044611D408
:4026400000F05EFC084AE300117803F1804303F5F04319705378147001335370BDE8104000F052BC10BD00BFE480FF1F30B504060CD411F4704509D1C40004F1804404F5C7
:40268000F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D9286800F019FCA4F5D6
:4026C0000044F6E7034B58686043BDE8384000F00FBC00BFEC80FF1F024B1B7A584300F007BC00BFEC80FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600197
:4027000042F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F00F021B6853F82200FD
:4027400043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B55B
:402780000F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD7C92FF1FCF
:4027C0002928000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054C06
:40280000A3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF7C92FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F8243003B19847013465
:40284000052CF8D138BD00BF8092FF1F024B03EB80035868596070477C92FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A0360127843EA0263B2
:4028800003600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC4600400201004901010049A0
:4028C00000010049050100490601004910B500F017FB204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52B4
:40290000137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F008031370CB
:40294000FFF7CAFE064B10222046BDE810401A6000F0DABAAB4300400E5900402F5B004080E200E008B500F0CBFA0F4A137803F0FE031370A2F5AA521D3A137803F0FD03A7
:402980001370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0B1BA00BF08590040044A137803F03F0343EA8010C0B21070704700BF28
:4029C00008590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F664
:402A0000C41393FBF1F305490B60054B1A8070470A590040F03900004A93FF1F4C93FF1F5093FF1F08B5102000F0A6F907210420FFF79AFE07490420FFF788FE064A0C206C
:402A4000137843F006031370FFF7BCFF034B00221A8008BD1D2B0000095900404893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABF9892FF1F044B1A789F
:402A800002F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF715FE024B1B78204610BD00BF09590040034A044BF3
:402AC0001B881088181A00B2704700BF5093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F3D7
:402B00000028D8BF5B42134493FBF1F000B270474A93FF1F4C93FF1F4893FF1F7047000010B500F0EDF9214A044613780A2043F001031370137C43F00103137412F80A3C06
:402B400043F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8DF
:402B8000062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0AFB900BFAB43004006590040275B004080E200E008B500F04F
:402BC0009FF90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F085B900BF94
:402C000000590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF60
:402C400033F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A80704702590040FA3900005693FF1F5C93FF1F5493FF1F08B51020DA
:402C800000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BD752D0000015900405893FF1F10B5054C23781BB92A
:402CC000FFF7DCFF01232370BDE81040FFF728BF9992FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0002404E0B4
:402D00002046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5493FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B8D
:402D400019680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475693FF1F5C93FF1F5893FF1F70470000034A00F0F8001378F0
:402D800003431370704700BF02410040034A00F0F800137803431370704700BF06410040014B1870704700BF78640040014B1870704700BF7965004073B515461E460B4CBF
:402DC00004230022019200920A46014618462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDFC80FF1F074A0223136002F688321268E0215064AD
:402E0000044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF7C640040014B1870704700BF7B640040014B1870704700BF7F6400400000000032
:402E4000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C460125002600F041F8814613
:402E800051460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B78234003430B705846043084
:402EC00000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BF01380046FCD17047FA
:402F0000EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E013790020704700600040D492FF1FB2
:402F4000002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B51E460E4C0025E41A7D
:402F8000A410A54204D056F8253098470135F8E700F0DEFD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD5C3B00005C3B00005C3B0000643B00007A
:402FC00003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029305934FF6FF730091F6
:403000000491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF7CBFF03B05DF804FB0F
:403040006081FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BF1D4653050FD52946F8
:4030800000F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A3894FF0FF3043F04003F2
:4030C000A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F08700002DE9F04F9DB00393CD
:403100008B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB130238DF82A3037463C461B
:4031400014F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A08000234FF0FF320493079332
:40318000059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A0744BF2B228DF853208C
:4031C00022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB02320121F5E701B107923B786F
:403200002E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB01210123F5E703B109
:40324000059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039B073323F007030833E3
:40328000039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB895B0601D4099801E019
:4032C0004FF0FF301DB0BDE8F08F00BF2B3B0000313B0000353B000000000000453000002DE9F04791461F460A698B6806469342B8BF1346C9F8003091F843200C46DDF815
:40330000208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD00135E368D9F800209B1A3C
:403340009D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430239463046C0470130D7
:40338000F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D00123224639463046C0470130D5D08C
:4033C00009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520BC
:40340000834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F14205111D19601368C2
:4034400084F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E021681A6811F0800F53
:4034800002D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F00F104051D6003D11E
:4034C000550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F0040020602BB9002D42
:403500007DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013C05F1FF35C5EB0E0360
:4035400023612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111D1960156800216268C6
:40358000284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF0FF3026E023692A46C6
:4035C00039463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B7890
:4036000004F1420584F842308AE705B0BDE8F083DF3900003C3B000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431E0A44914204D011F885
:40364000014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B02F8014FF8E710BD08
:4036800038B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B681218226063600C6023E087
:4036C000A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018824201BF1068526861
:403700000918216062605C602846BDE8384000F098B838BDA092FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E000F082F8234B1C6831
:403740001A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304600F052F8206029464B
:40378000304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F80130EBD10C2333609D
:4037C000304600F03EF8002070BD00BFA092FF1F9C92FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F885420ED929463846FFF7AD
:403800008BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7D6FB431C02D1236803B12B6038BD8493FF1F15
:403840007047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F021102130215027265706C792030782530327800686F6D696E670074
:40388000626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E6973686564207365656B00796573006E6F00647269766520303A202573206472D4
:4038C00069766520313A2025730057616974696E6720666F72205553422E2E2E0055534220726561647900636F6D6D616E6420307825303278006661696C2025642B2564B5
:403900002B2564203D3D2025642C206E6F74202564007061737365643D256400756E64657272756E206166746572202564207061636B65747300636F756E743D2564206933
:403940003D256420643D256400636D645F777269746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D256420756E64657272756E3D51
:40398000256400756E64657272756E2100737563636573730073746172742065726173696E670073746F702065726173696E670069646C65000051004010004051004030AB
:4039C0000000000140001000140140000800400140000A004C0140000200500140200030313233343536373839414243444546000001000000040000001000010000000492
:403A00000000001028000000000104000100000000000000000157494E5553420000303030303100000000000000000012034D00530046005400310030003000010000009D
:403A400001000000483A000001000000173B0000000000000000000001000000603A000001000000E93A000004000000823A0000000000000000000000000000803A000037
:403A8000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E00670069006E0065002A03A3
:403AC00043006F0077006C00610072006B00200054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FF0000010705010240A2
:403B00000000070582024000000705030340000A0705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C00656667454647003031323359
:403B400034353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000059100000F8B500BFF8BC08BC9E46704735000000883B0000C880FF1FA0000000EB
:403B80002012000000000000000000008893FF1FFF000000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003FF0000003E
:403BC0000000000000000000000000000000000000000000000000000000000000000000293B00000000000000000000000000000000000000000000000000000000000061
:403C000000000000000000000000000000000000000000000000000000000000000000000081FF1F00000000000000000000000000000000000000000000000000000000E5
:403C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044
:403C80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
:403CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C4
@@ -4098,55 +4098,55 @@
:40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041
:40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
:0200000480007A
:400000000145004008520040015B0040016400400103014008050140440601404807014051080140520901404B0A01404E0B0140650C0140450D0140580E0140350F0140DA
:40004000471401405B150140221701403F18014056190140481A0140561B01400A4001400E4101400E420140084301400244014009450140064601400B4701400F48014016
:4000800011490140174C0140094D014007500140045101407E0208020901100211821902601661157C4027212D0AE21029022B4088408CA88E808F05E628EA08003F032039
:4000C0000538062007010A380B200E070F3811041220131016201707183819021A011B381C041D041E101F082220232024042608272028022A382B202D3F2E20353F363F03
:40010000580459045B045C995F01822086208A078C3F910292209438950196019A209C049E10A220A404A608A802AA38AE38B102B301B43FBF05D608D804D904DB04DC0965
:40014000DD90DF01012A030205490604082009080A010B800C100D040E010F801210136114021690172019301D402040220427042C042D082F4232083301358036023724A4
:400180003D043E203F815A605B08600862486A806C046D8085108728880689018B408C0891089301960898A099029AA0A008A344A440A540AB40AD04AE20B140B204C0FFAF
:4001C000C2FFC4FFCAF0CCF3CEF0D60ED80EE20BEA0CEE20020206040760080C09070A200B100C800D770E0C10FF112013101403152016FC1A081B071C0C1E101F08200CD3
:40020000210122402306240125012602270229012A022B042C0C2D012EF1310F334036FF37303B803E40580459045B045C905F0181108204860489148A078B218C078D7F65
:40024000920495099604981899019A029B0E9D089E109F01A008A604A750A909AA01AD01AE04AF22B21FB330B50FB740BE04D804D904DC90DF01009001010310040A052049
:400280000701081009100A820B200E420F141190120A13091404150A174019041D402019211822102308250126842741280829082A802C882E042F02325033043540360469
:4002C0003721381139403E403F186C026D40850888048B088D088E048F04908A910A9244933494309531980499309A049B309C109E80A088A203A302A446A509A680A70142
:40030000AB10AD20B020C0FFC2FFC4FFCAFECCFECE7DE040E229E680E88000010104033909051120160119341B0A1D0528022904311033203401350C360237033E503F047C
:40034000580459045B045C905F018077831186E4871188168A218B778D778E1490119288930E96779A149BE19E14A214A410A511A642A744A911AA14AB22AC01AD11AE0EF7
:40038000AF88B10FB20FB3F0B4F0BA28BB0AC006C60EC70CC814C9FFCAFFCBFFD004D601D804D904DA04DB04DC99DD09DF01E2C0000104A406800A200C020D180F02120442
:4003C00014121501171019401B101C041D341F1020012104221923202480260227102B042C122D1032513602371038013B103C983F024F2057845C205D806AC06B016C893C
:400400006D806E046F027649878088848C028D148E0290089102931494029508964499A09A049D409E499F21A201A488A541A688A702AE40C0F8C2F4C4F2CA74CCADCEF509
:40044000D220D630E250E610EA08EE02004001120220047E0512083E09080A400B130D121112120114401610184019111A081C401E041F02230424802710288029122A7E0E
:400480002C402D182E0231083310350736FF39203E403F05580459045C905F01808081248204852486048804891A8A208BE48E0290FF916C93809404950A9610971098035A
:4004C00099249AFC9D409E029F84A004A208A301A404A524A6F9A801A924AA02AC04AE40AF10B1F9B2FFB307B90ABE04C035C702C810C9FFCAFFCBFFCD20CEF0D110D80495
:40050000D904DA04DC90DD09DF01E108E240E340E480E640E74000040120028103400402062809480A680C880E440F801102120813441603173819081F802101220623502F
:40054000249C26102708281029202B422D202E402FA0329A3410369139013B143D033E203F5440104940520483018C4093069408950A9644975099A09D089E429F59A108DA
:40058000A201A320A488A501A6CAA702C07FC2FFC4EFCAFFCCBFCEF7D002D208E210E404004001010280030E04020501060C07040872090F0A800C080E041080122014047E
:4005C0001608170F18101C101D011F022010210F24102710280E29012AF02B082D012E0134FF351F3820580459045C995F018201831086108708881089078BF88C1F8DFF45
:4006000090409118934094099612970799189A069B209C109D809E089F18A010A118A3E0A40CA501A601A706A901AA20AB02AD01AE10AF04B040B238B3FFB407B638BE018F
:40064000BF04D608D804D904DB04DC09DD90DF010120028003120408052006410701080409800B440C020E0A0F10110212011318150A176019201D881E0A1FA12304272093
:40068000280429082A012B022C0C2D042E042F82308032083520368A39043A023B103D023E403F145E8065806C02C0FFC2FFC4FFCAFFCCFACEF7D610D810000A080D0F01A1
:4006C00012051C011D04201029012D02301031033403360C37043E013F40420345E0480249FF4AFF4BFF4F83580A59045A045B045C995D095F01801182888320840185381E
:40070000860E870188108A228B208E148F20913F921495049614970899029A049B389EE49F07A214A338A477A720AA77AB20AC16AD04AE41AF10B13FB20FB4F0B6F0BAA8F4
:40074000D804D904DC99DF010120024005050602070809020A020C080E010F8811021525160119201A021B021D041E011F60204021022280274029202A442B202C0A2E028D
:400780002F2032403501376838403D053E203F804108484059125B44608061646B017808810186018810892C8B208D0291219280950496029780988899519A639B2C9C104A
:4007C0009E049F01A00AA344A440A508A680A702A802AA01AD88AE02AF08B201B440B640C0FAC2F9C4F8CAFCCCF8CEF8D002D60FD80FDE02E008E212E609EA0CEE0A8202FC
:40080000869087028A01904091219280950496419780988899519A609B249C109D889E149F01A008A344A440A508A602A901AB02B404B610E008E243E408E614E840EA0CDF
:40084000EE08021006040A040E041007150216071A021E041F0120082104260428182A012D072E0433043502361F37013E40580459045B045C905F018101822085F1862038
:4008800088048A018B448C018E028F22900191CC923E932295AA962097449A109E20A240A311A620AA08AC7FAD88B03FB50FB640B7F0D608D804D909DB04DC99DD90DF01DE
:4008C00000900101031004820510071009500A800B200D290F221002120813091488160217281A821B011F802305242225302608272429012A402C022F80300833013480CD
:40090000350439503B0558405D80624069506A106E807B04815085808720884089208B408E108F20909091209320942095019682970199149B189C089E809F04A002A1482A
:40094000A30CA440A510A608A701A802B708C0FFC2FFC4FFCA99CC53CE0FD618D808DE04E240E408E690E810EA08EE0802010320070708100B200E020F20100C11FF1202C4
:400980001504160417901B201D041F48200821F823012401250227F8280A2A042B382F20300F31E03210333F340E3E14580459045B045C995F0182078404860889068A20DB
:4009C0008B098D098E388F06903F940296389A209E209F0CA220A404A610AA20AC38AE01B104B308B501B63FB702B9AABF55D608D804D904DB04DC09DD90DF01014203085E
:400A00000481051007200A900C020D100F2210081240130415011794190B1B201C011F8020A021052380240825042682288029102B11308233143640388039293D904080E9
:400A4000410142805F40640268086A806B016D90800285018682885089018A028C048D048F809080916092109344950399109A409B349D099E829F01A082A240A319A44806
:400A8000A720A920AC80AE08AF01B080B108C0FBC2FCC4FECA0FCC1FCE3FD610D810E080E270E6A0E860EE421B011F0831103308368037023B40C630CCF0CE103002338003
:400AC000358037043B083D808F0297409D109F02AE80AF01CCF0CE60500857108704894097489D109F04A402A540A780AD80D460E610EA10830497409D109F04A402A790B8
:400B0000B008E2801420C4045F80630883048F889820D604D802E208E4081B0898209C06A708AC06C608080809200C040F01830186018D0898209C06C20FE4082680838010
:400B40008D108E0297409D10A680A710AC02AF80B680C820E620EA10EE105202538078407D80844085408F10A202A380A710AF40D4A0DC80DE20E240E610EE40052008023A
:400B80000E411F105280550858406302870188208920960198209C02A108AC08AD20C001C20DC601D407D602E20577108F10AF10B040B6C0DE04E402E803EE0201010B01AE
:400BC0000D010F0111011B011D0100FF01AB020211050000BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B8470047000001000053
:400C0000800000028200820000000000000303000300000027001801270018010004000000050000000000000000000000000000000000000000000000000000000000009C
:400000000145004009520040015B0040016500400101014003030140010501400307014052080140500901404F0A0140480B0140490C0140470D0140400E0140360F01409E
:4000400002150140031701405C18014057190140471A0140531B01400B400140124101400B4201400D43014002440140074501400B4601400C470140104801401149014069
:40008000154C01400D4D014005500140045101407E02080209411080110218041902600C61157C402721290AE204E601EA20EE02E202E608EA10EE04000801080404050568
:4000C0000708080809080C400D080E1011011201130E143A16401708180819081C021E042305240825042701280629082A782C202D012E402F02307F310F327F330F380AE4
:400100003B0A580459045B045C995F018203830187018A108C018D018E248F08907C9282930E961097019A809D019E1C9F04A101A210A302A510A610AA10AB01AC01AE48FD
:40014000B01FB10FB4F0B510BA20BB02BF10D608D80BD90BDB04DC99DD90DF01012202010488051007800A860B100C020DA00E2011011208134414021504162017201804BE
:4001800019021A041B011CA01E202008220126012790282029202A012C022E282F4032493514360139013B443C803D2A5980614069906F0278417B0288049141928093D450
:4001C00094249602970A982299249A019B409C019D019EC0A188A208A402A504A709B404B502B604C0FBC2FFC4EFCAF7CCEBCEFBD608D808DE01E608EA10EE0400FC0108A1
:4002000002020310071408200AD00BE40CE10D100E080F8212031314161017141D1F1E101F4020802110223C2321261028402A302B142CE12E04301F310F33F034E03B08A2
:40024000580B590B5B045C995F0180368209852A86128740882D89098A128B148E098F209080924093C09440968098079C249D089F13A03FA180A320A840A908ADEAB0076D
:40028000B1E0B280B301B438B518B640B706BE44D808D904DB04DC99DF0100820120040A07050A420B100EA80F01110112091304171219201A401D8A1E081F4020602101B0
:4002C000231025102652275029202A012C022F483249364239033B543D283E806102620169406C0C6F0178017F0187058C028D04900292809345946495029642971298206E
:4003000099049B069C019D219E449F01A041A208A418A701B040B204B7C0C0FBC2FDC45FCAB5CC9BCE7FDE11E40800020104040806140A010C1C101012081301140215050D
:400340001601170220032B022D062E022F0130043107320336183A083F015608580459045B045C095D905F018128829083118401853F86448A9C8C208D3F8EC090C191FFA2
:40038000922895149690972199019A039B0E9D409E90A0FCA202A902AA90AB01ADC0AE90B140B330B41FB50FB6E0B780BA80D608D80BD90BDB04DC99DD90DF0101A604240E
:4003C0000501070208400A240B400EA0120113121401164019801A041B401D841EA02080229C2344260129602C082F0230023294370539063A053B403F01580259025A0213
:400400005B405F806140670168036A806B026C016F06780182019440950296E4974698209C429D209E049F02A412A580A741B240B420B540C0FFC23FC49BCA3CCCCFCE8BAA
:40044000D618D818DE01EA20EE0203900401060407900A100B9C0D010E100F4812101520161017C01A031B031DFC1E1C1F02201C2202239025C127242A102B902C012E08A9
:400480002F9033E0341F351F3B08420147E0482049FF4AFF4BFF4F83580859085A045B045C995D095F018310871C8B108D018F0893109F03A310A501A704AB10AD1CAF0254
:4004C000B71FD908DB04DC90DF0100400120032008420A281110134219101A0820042120220826802701284229202A042B202C422D202F20312032483402368039123B4544
:400500003C203D103F454180520159105A445B0160046208638269406E8078807C807F018201C007C20FC40BCAFFCC9ECEFFD008D60FD80FDE18EA01EE08E020E623EE0BF2
:40054000022005880612083209880C080E010F011049118812321432151017A0182119461A1C1BB81D9A1E091F20206321422204230424322588282029882A022C322D04B6
:400580003020313F324033C1341F37C1398A3A203E055608580459045B045C995D905F018401860289018D028E039002920199089B049D049F08A004B002B102B204B304D8
:4005C000B401B501B708B822B988BE15BF55C043C520C802C9FFCAFFCBFFCD20CEF0D110D804D904DA04DB04DD09DF01E108E240E340E480E640E740000801400320051496
:40060000060107400A800D800EA8108016441799181019021B101C401E081F0420012111220424A02505264227A02D202F52324033203504360137A039903C243E4040504B
:40064000488049204A0459405A205D045E085F4064016580670268046A806C036E406F01831085118B4491D0938094A89504970A984099449B509C019D119EC89F20A0500A
:40068000A188A211A402A535A708A980C0F5C2F8C4F8CAF0CCFCCE7CD003D61CD810E022E620EA04EE0B010129022C013102350136013E403F115608580459045B045D9048
:4006C0005F0180478208830885218626874E880189018A388B708D4E8E40914E92029426974E98269B029D809E11A010A201A321A520A604A701A811AA26AB04AC26AD0FF1
:40070000AF10B040B380B43FB57FB63FBAA0BB20BF04C003C50EC70CC811C9FFCAFFCBFFD004D601D804D904DA04DB04DC99DD09DF01E2C0040205940DA20E201002150580
:40074000162017A01B401D111E011F102040221024042530260827082B042E642F40344035043602371038013C803D283E0145084F04570858905C406001630868026C042B
:400780006D016E046F02768981028410860888808D028E049002910492409380940C96209724984099869A049BB09D119E899F08A040A18CA212A530A74AA881AE04B74049
:4007C000C0F0C2F0C4F1CAF4CCF0CEF1D040D61CD80CE008E404E602E8041B011F083180330836843B408340C630CCF0CE10E22032043380364037023B043F808180A0042B
:40080000A340A580A604AE80AF41B004CCF0CE60E680EE40531057208510960897049F02A004A644A780D460E240860491209608972498809F02A004A640A7A0AA04B48062
:40084000E610EE201680C40458405E019A80A404AC04D401D6011B04844096019C40A404A710B680B710C608EA08EE0808080B080E020F4087048A209601A404A710AB043D
:40088000C20FE00425808004871089808B04912097249880A004A720AB02AE40AF80C820E6C0EED0511054045880700477809008912098809B80A004AB80AF20D4E0DC8000
:4008C000DE20EA80EE1005200A400C100F201C0852225620610186029641A220A404A710AA41AC08AF40C001C20DC601D407D80270018001852086018810960198109920A0
:40090000AA20B501DC01E204EC0201010D010F0111011D0100FF01AB02021105BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B88D
:4009400047004700000100008000000282008200000000000007070007000000270018012700180100040000000500000000000000000000000000000000000000000000C4
:400980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037
:4009C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F7
:400A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B6
:400A40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076
:400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036
:400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6
:400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5
:400B40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075
:400B80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035
:400BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F5
:400C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B4
:400C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074
:400C80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034
:400CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F4
@@ -4615,12 +4615,12 @@
:0200000490105A
:04000000BC90ACAF55
:0200000490303A
:0200000043BA01
:0200000096F078
:0200000490402A
:4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0
:400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
:400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040
:4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
:0200000490501A
:0C00000000012E16106900002E30542361
:0C00000000012E16106900002E2FA759D9
:00000001FF

View File

@@ -814,7 +814,7 @@
</Group>
<Group key="Component">
<Group key="v1">
<Data key="cy_boot" value="cy_boot_v5_80" />
<Data key="cy_boot" value="cy_boot_v5_81" />
<Data key="Em_EEPROM_Dynamic" value="Em_EEPROM_Dynamic_v2_20" />
<Data key="LIN_Dynamic" value="LIN_Dynamic_v5_0" />
</Group>
@@ -871,6 +871,7 @@
<Data key="c5367cde-21d5-4866-9a32-d16abfea0c61" value="WPT" />
<Data key="d19368c5-6855-41bb-a9ff-808938abef00" value="INDEX" />
<Data key="e9f14b5a-b2bf-49b8-98f3-d7b5a43ace8d" value="DRVSB" />
<Data key="e16b5ef8-00d3-40a4-bc1c-194983c8eb3d" value="LOW_CURRENT" />
<Data key="e851a3b9-efb8-48be-bbb8-b303b216c393" value="INDEX300" />
<Data key="e51063a9-4fad-40c7-a06b-7cc4b137dc18" value="DSKCHG" />
<Data key="ea7ee228-8b3f-426c-8bb8-cd7a81937769" value="DIR" />
@@ -4161,6 +4162,11 @@
<Data key="Port Format" value="12,3" />
</Group>
</Group>
<Group key="e16b5ef8-00d3-40a4-bc1c-194983c8eb3d">
<Group key="0">
<Data key="Port Format" value="3,2" />
</Group>
</Group>
<Group key="e851a3b9-efb8-48be-bbb8-b303b216c393">
<Group key="0">
<Data key="Port Format" value="3,0" />

View File

@@ -2847,6 +2847,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="TK43_REG" persistent="">
<Hidden v="False" />
</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="TK43_REG.h" persistent="Generated_Source\PSoC5\TK43_REG.h">
<Hidden v="False" />
</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="TK43_REG.c" persistent="Generated_Source\PSoC5\TK43_REG.c">
<Hidden v="False" />
</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="TK43_REG_PM.c" persistent="Generated_Source\PSoC5\TK43_REG_PM.c">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;CortexM3;;;" />
<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="TK43" persistent="">
<Hidden v="False" />
</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="TK43_aliases.h" persistent="Generated_Source\PSoC5\TK43_aliases.h">
<Hidden v="False" />
</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="TK43.c" persistent="Generated_Source\PSoC5\TK43.c">
<Hidden v="False" />
</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="TK43.h" persistent="Generated_Source\PSoC5\TK43.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>
<filters />
</CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0>
</dependencies>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>

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,77 +18,62 @@ module Sampler (
//`#start body` -- edit after this line, do not edit this line
localparam STATE_WAITING = 0;
localparam STATE_OPCODE = 1;
// NOTE: Reset pulse is used in both clock domains, and must be long enough
// to be detected in both.
reg [0:0] state;
reg [5:0] counter;
reg oldsampleclock;
reg oldindex;
reg oldrdata;
reg index_q;
reg rdata_q;
reg sampleclocked;
reg indexed;
reg rdataed;
reg index_edge;
reg rdata_edge;
assign req = (state == STATE_OPCODE);
reg req_toggle;
always @(posedge clock)
always @(posedge sampleclock)
begin
if (reset)
begin
state <= STATE_WAITING;
opcode <= 0;
sampleclocked <= 0;
indexed <= 0;
rdataed <= 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
begin
/* Remember positive egdes for sampleclock, index and rdata. */
/* 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;
if (sampleclock && !oldsampleclock)
sampleclocked <= 1;
oldsampleclock <= sampleclock;
rdata_edge <= rdata && !rdata_q;
rdata_q <= rdata;
if (index && !oldindex)
indexed <= 1;
oldindex <= index;
if (rdata && !oldrdata)
rdataed <= 1;
oldrdata <= rdata;
case (state)
STATE_WAITING:
begin
if (sampleclocked)
begin
if (rdataed || indexed || (counter == 6'h3f))
begin
opcode <= {rdataed, indexed, counter};
rdataed <= 0;
indexed <= 0;
counter <= 1; /* remember to count this tick */
state <= STATE_OPCODE;
end
else
counter <= counter + 1;
sampleclocked <= 0;
end
end
STATE_OPCODE: /* opcode sent here */
begin
state <= STATE_WAITING;
end
endcase
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

View File

@@ -65,7 +65,8 @@ begin
if (dataclocked)
begin
pulsepending <= opcode[7];
countdown <= opcode[5:0] - 1; /* compensate for delay in last tick */
countdown <= opcode[5:0];
state <= STATE_WRITING;
end
end

View File

Binary file not shown.

View File

@@ -13,8 +13,8 @@
#define DISKSTATUS_WPT 1
#define DISKSTATUS_DSKCHG 2
#define STEP_TOWARDS0 1
#define STEP_AWAYFROM0 0
#define STEP_TOWARDS0 0
#define STEP_AWAYFROM0 1
static bool drive0_present;
static bool drive1_present;
@@ -249,6 +249,8 @@ static void seek_to(int track)
CyWdtClear();
}
CyDelay(STEP_SETTLING_TIME);
TK43_REG_Write(track < 43); /* high if 0..42, low if 43 or up */
print("finished seek");
}
@@ -381,8 +383,9 @@ static void init_capture_dma(void)
static void cmd_read(struct read_frame* f)
{
SIDE_REG_Write(f->side);
seek_to(current_track);
SIDE_REG_Write(f->side);
STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */
/* Do slow setup *before* we go into the real-time bit. */
@@ -478,6 +481,7 @@ abort:;
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
deinit_dma();
STEP_REG_Write(0);
if (saved_dma_underrun)
{
print("underrun after %d packets");
@@ -523,9 +527,11 @@ static void cmd_write(struct write_frame* f)
return;
}
SEQUENCER_CONTROL_Write(1); /* put the sequencer into reset */
seek_to(current_track);
SIDE_REG_Write(f->side);
STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */
SEQUENCER_CONTROL_Write(1); /* put the sequencer into reset */
{
uint8_t i = CyEnterCriticalSection();
REPLAY_FIFO_SET_LEVEL_MID;
@@ -533,18 +539,17 @@ static void cmd_write(struct write_frame* f)
REPLAY_FIFO_SINGLE_BUFFER_UNSET;
CyExitCriticalSection(i);
}
seek_to(current_track);
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;
dma_writing_to_td = 0;
dma_reading_from_td = -1;
dma_underrun = false;
int old_reading_from_td = -1;
for (;;)
{
@@ -638,6 +643,7 @@ abort:
deinit_dma();
STEP_REG_Write(0);
if (dma_underrun)
{
print("underrun!");

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

@@ -82,7 +82,7 @@ people who've had it work).
| [Brother 120kB](doc/disk-brother.md) | 🦄 | | |
| [Brother 240kB](doc/disk-brother.md) | 🦄 | 🦄 | |
| [Brother FB-100](doc/disk-fb100.md) | 🦖 | | Tandy Model 100, Husky Hunter, knitting machines |
| [Macintosh 800kB](doc/disk-macintosh.md) | 🦖 | | and probably the 400kB too |
| [Macintosh 800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | and probably the 400kB too |
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
{: .datatable }
@@ -103,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

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

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

@@ -118,10 +118,10 @@ static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status)
uint8_t decode_side(uint8_t side)
{
/* Mac disks, being weird, use the side byte to encode both the side (in
* bit 5) and also whether we're above track 0x3f (in bit 6).
* bit 5) and also whether we're above track 0x3f (in bit 0).
*/
return !!(side & 0x40);
return !!(side & 0x20);
}
AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord()

242
arch/macintosh/encoder.cc Normal file
View File

@@ -0,0 +1,242 @@
#include "globals.h"
#include "record.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "macintosh.h"
#include "crc.h"
#include "sectorset.h"
#include "writer.h"
#include "fmt/format.h"
#include <ctype.h>
FlagGroup macintoshEncoderFlags;
static DoubleFlag postIndexGapUs(
{ "--post-index-gap-us" },
"Post-index gap before first sector header (microseconds).",
0);
static DoubleFlag clockCompensation(
{ "--clock-compensation-factor" },
"Scale the output clock by this much.",
.9724);
static bool lastBit;
static double clockRateUsForTrack(unsigned track)
{
if (track < 16)
return 2.623;
if (track < 32)
return 2.861;
if (track < 48)
return 3.148;
if (track < 64)
return 3.497;
return 3.934;
}
static unsigned sectorsForTrack(unsigned track)
{
if (track < 16)
return 12;
if (track < 32)
return 11;
if (track < 48)
return 10;
if (track < 64)
return 9;
return 8;
}
static int encode_data_gcr(uint8_t gcr)
{
switch (gcr)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
};
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
* and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
*/
static Bytes encode_crazy_data(const Bytes& input)
{
Bytes output;
ByteWriter bw(output);
ByteReader br(input);
uint8_t w1, w2, w3, w4;
static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3;
uint8_t b1[LOOKUP_LEN + 1];
uint8_t b2[LOOKUP_LEN + 1];
uint8_t b3[LOOKUP_LEN + 1];
uint32_t c1 = 0;
uint32_t c2 = 0;
uint32_t c3 = 0;
for (int j=0;; j++)
{
c1 = (c1 & 0xff) << 1;
if (c1 & 0x0100)
c1++;
uint8_t val = br.read_8();
c3 += val;
if (c1 & 0x0100)
{
c3++;
c1 &= 0xff;
}
b1[j] = (val ^ c1) & 0xff;
val = br.read_8();
c2 += val;
if (c3 > 0xff)
{
c2++;
c3 &= 0xff;
}
b2[j] = (val ^ c3) & 0xff;
if (br.pos == 524)
break;
val = br.read_8();
c1 += val;
if (c2 > 0xff)
{
c1++;
c2 &= 0xff;
}
b3[j] = (val ^ c2) & 0xff;
}
uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2);
b3[LOOKUP_LEN] = 0;
for (int i = 0; i <= LOOKUP_LEN; i++)
{
w1 = b1[i] & 0x3f;
w2 = b2[i] & 0x3f;
w3 = b3[i] & 0x3f;
w4 = ((b1[i] & 0xc0) >> 2);
w4 |= ((b2[i] & 0xc0) >> 4);
w4 |= ((b3[i] & 0xc0) >> 6);
bw.write_8(w4);
bw.write_8(w1);
bw.write_8(w2);
if (i != LOOKUP_LEN)
bw.write_8(w3);
}
bw.write_8(c4 & 0x3f);
bw.write_8(c3 & 0x3f);
bw.write_8(c2 & 0x3f);
bw.write_8(c1 & 0x3f);
return output;
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{
for (bool bit : src)
{
if (cursor < bits.size())
bits[cursor++] = bit;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{
cursor += width;
for (int i=0; i<width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
}
static uint8_t encode_side(uint8_t track, uint8_t side)
{
/* Mac disks, being weird, use the side byte to encode both the side (in
* bit 5) and also whether we're above track 0x3f (in bit 0).
*/
return (side ? 0x20 : 0x00) | ((track>0x3f) ? 0x01 : 0x00);
}
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector)
{
if ((sector->data.size() != 512) && (sector->data.size() != 524))
Error() << "unsupported sector size --- you must pick 512 or 524";
write_bits(bits, cursor, 0xff, 1*8); /* pad byte */
for (int i=0; i<7; i++)
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
write_bits(bits, cursor, MAC_SECTOR_RECORD, 3*8);
uint8_t encodedTrack = sector->physicalTrack & 0x3f;
uint8_t encodedSector = sector->logicalSector;
uint8_t encodedSide = encode_side(sector->physicalTrack, sector->logicalSide);
uint8_t formatByte = MAC_FORMAT_BYTE;
uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1*8);
write_bits(bits, cursor, encode_data_gcr(encodedSector), 1*8);
write_bits(bits, cursor, encode_data_gcr(encodedSide), 1*8);
write_bits(bits, cursor, encode_data_gcr(formatByte), 1*8);
write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1*8);
write_bits(bits, cursor, 0xdeaaff, 3*8);
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
write_bits(bits, cursor, MAC_DATA_RECORD, 3*8);
write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1*8);
Bytes wireData;
wireData.writer().append(sector->data.slice(512, 12)).append(sector->data.slice(0, 512));
for (uint8_t b : encode_crazy_data(wireData))
write_bits(bits, cursor, encode_data_gcr(b), 1*8);
write_bits(bits, cursor, 0xdeaaff, 3*8);
}
std::unique_ptr<Fluxmap> MacintoshEncoder::encode(
int physicalTrack, int physicalSide, const SectorSet& allSectors)
{
if ((physicalTrack < 0) || (physicalTrack >= MAC_TRACKS_PER_DISK))
return std::unique_ptr<Fluxmap>();
double clockRateUs = clockRateUsForTrack(physicalTrack) * clockCompensation;
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
fillBitmapTo(bits, cursor, postIndexGapUs / clockRateUs, { true, false });
lastBit = false;
unsigned numSectors = sectorsForTrack(physicalTrack);
for (int sectorId=0; sectorId<numSectors; sectorId++)
{
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
write_sector(bits, cursor, sectorData);
}
if (cursor >= bits.size())
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap;
}

View File

@@ -1,11 +1,17 @@
#ifndef MACINTOSH_H
#define MACINTOSH_H
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#define MAC_SECTOR_RECORD 0xd5aa96 /* 1101 0101 1010 1010 1001 0110 */
#define MAC_DATA_RECORD 0xd5aaad /* 1101 0101 1010 1010 1010 1101 */
#define MAC_SECTOR_LENGTH 524 /* yes, really */
#define MAC_ENCODED_SECTOR_LENGTH 703
#define MAC_FORMAT_BYTE 0x22
#define MAC_TRACKS_PER_DISK 80
class Sector;
class Fluxmap;
@@ -22,5 +28,17 @@ public:
std::set<unsigned> requiredSectors(Track& track) const;
};
class MacintoshEncoder : public AbstractEncoder
{
public:
virtual ~MacintoshEncoder() {}
public:
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
};
extern FlagGroup macintoshEncoderFlags;
#endif

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

@@ -292,7 +292,7 @@ INDEX300 ---+ 3.0| | GND+--------------------------+
+----+ +----+ +--+--+ |
INDEX360 ---+ 3.1| | 1.7+------ DISKCHG --+34+33+--+
+----+ +----+ +--+--+
+ 3.2| | 1.6+------- SIDE1 ---+32+31+
TK43 ---+ 3.2| | 1.6+------- SIDE1 ---+32+31+
+----+ +----+ +--+--+
+ 3.3| | 1.5+------- RDATA ---+30+29+
+----+ +----+ +--+--+
@@ -306,7 +306,7 @@ INDEX360 ---+ 3.1| | 1.7+------ DISKCHG --+34+33+--+
+----+ +----+ +--+--+
+15.0| | 1.0+------- STEP ----+20+19+
+----+ +----+ +--+--+
+15.1| |12.0+-------- DIR ----+18+17+
+15.1| |12.0+--- DIR/SIDE1 ---+18+17+
+----+ +----+ +--+--+
+15.2| |12.1+------- MOTEB ---+16+15+
+----+ +----+ +--+--+
@@ -343,6 +343,10 @@ INDEX360 ---+ 3.1| | 1.7+------ DISKCHG --+34+33+--+
Notes:
- `DIR/SIDE1` is the step direction pin. During reads or writes, `SIDE1` is
also multiplexed onto it, because some drives expect this. This is harmless
on other drives because the `DIR` pin is ignored during reads or writes.
- `TX` is the debug UART port. It's on pin 12.7 because the board routes it
to the USB serial port on the programmer, so you can get debug information
from the FluxEngine by just plugging the programming end into a USB port
@@ -365,6 +369,10 @@ Notes:
rather exotic things. See the section on flippy disks [in the FAQ](faq.md)
for more details; you can normally ignore these.
- `TK43` is an optional output pin which goes low when the drive is seeking
to track 43 or above. This is useful when using 8" floppy drives, which
require reduced write current when writing to these tracks.
## Next steps
You should now be ready to go. You'll want to read [the client

View File

@@ -45,22 +45,34 @@ 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).
**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 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`.
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`.
Writing discs
-------------
Just do:
```
fluxengine write mac -i mac.diskcopy
```
It'll read the DiskCopy 4.2 file and write it out.
The same warning as above applies --- you can use normal `.img` files but it's
problematic. Use DiskCopy 4.2 files instead.
Useful references
-----------------
@@ -74,3 +86,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

@@ -32,3 +32,11 @@ If you've got a 40-track disk, use `-s :t=0-79x2`.
If you've got a single density disk, use `--read-fm=true`. (Double density is
the default.)
Useful references
-----------------
- [The JV3 file format](https://www.tim-mann.org/trs80/dskspec.html):
documents the most popular emulator disk image.

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,14 @@ 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).
- `.jv3`: a disk image format mainly used by the TRS-80. These images can be
read, but not yet written. You only get the data; the density and DAM bits
are ignored.
### High density disks
High density disks use a different magnetic medium to low and double density
@@ -209,21 +232,16 @@ directory.
format in a non-backwards-compatible way; this tool will upgrade flux files
to the new format.
- `fluxengine convert`: converts flux files from various formats to various
other formats. You can use this to convert Catweasel flux files to
- `fluxengine convert`: converts files from various formats to various other
formats. The main use of this is probably `fluxengine convert image`, which
will convert a disk image from one format to another.
There are also subcommands for converting Catweasel flux files to
FluxEngine's native format, FluxEngine flux files to various other formats
useful for debugging (including VCD which can be loaded into
[sigrok](http://sigrok.org)), and bidirectional conversion to and from
Supercard Pro `.scp` format.
**Important SCP note:** import (`fluxengine convert scptoflux`) should be
fairly robust, but export (`fluxengine convert fluxtoscp`) should only be
done with great caution as FluxEngine files contain features which can't be
represented very well in `.scp` format and they're probably pretty dubious.
As ever, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new) with any
reports.
Commands which normally take `--source` or `--dest` get a sensible default if
left unspecified. `fluxengine read ibm` on its own will read drive 0 and
write an `ibm.img` file.

View File

@@ -41,6 +41,12 @@ Bytes::Bytes(const uint8_t* ptr, size_t len):
_high(len)
{}
Bytes::Bytes(const std::string& s):
_data(createVector((const uint8_t*)&s[0], s.size())),
_low(0),
_high(s.size())
{}
Bytes::Bytes(std::initializer_list<uint8_t> data):
_data(createVector(data)),
_low(0),
@@ -258,6 +264,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);
@@ -38,6 +41,8 @@ public:
uint8_t* begin() { checkWritable(); return &(*_data)[_low]; }
uint8_t* end() { checkWritable(); return &(*_data)[_high]; }
operator const std::string () const { return std::string(cbegin(), cend()); }
void boundsCheck(unsigned pos) const;
void checkWritable();
void adjustBounds(unsigned pos);
@@ -57,6 +62,7 @@ public:
ByteWriter writer();
void writeToFile(const std::string& filename) const;
void writeTo(std::ostream& stream) const;
private:
std::shared_ptr<std::vector<uint8_t>> _data;
@@ -270,6 +276,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

@@ -4,6 +4,9 @@
Fluxmap& Fluxmap::appendBytes(const Bytes& bytes)
{
if (bytes.size() == 0)
return *this;
return appendBytes(&bytes[0], bytes.size());
}

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;

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" },

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

@@ -0,0 +1,128 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
#include <fstream>
class DiskCopyImageReader : public ImageReader
{
public:
DiskCopyImageReader(const ImageSpec& spec):
ImageReader(spec)
{}
SectorSet readImage()
{
std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary);
if (!inputFile.is_open())
Error() << "cannot open input file";
Bytes data;
data.writer() += inputFile;
ByteReader br(data);
br.seek(1);
std::string label = br.read(data[0]);
br.seek(0x40);
uint32_t dataSize = br.read_be32();
br.seek(0x50);
uint8_t encoding = br.read_8();
uint8_t formatByte = br.read_8();
unsigned numCylinders = 80;
unsigned numHeads = 2;
unsigned numSectors = 0;
bool mfm = false;
switch (encoding)
{
case 0: /* GCR CLV 400kB */
numHeads = 1;
break;
case 1: /* GCR CLV 800kB */
break;
case 2: /* MFM CAV 720kB */
numSectors = 9;
mfm = true;
break;
case 3: /* MFM CAV 1440kB */
numSectors = 18;
mfm = true;
break;
default:
Error() << fmt::format("don't understant DiskCopy disks of type {}", encoding);
}
std::cout << "reading DiskCopy 4.2 image\n"
<< fmt::format("{} cylinders, {} heads; {}; {}\n",
numCylinders, numHeads,
mfm ? "MFM" : "GCR",
label);
auto sectorsPerTrack = [&](int track) -> int
{
if (mfm)
return numSectors;
if (track < 16)
return 12;
if (track < 32)
return 11;
if (track < 48)
return 10;
if (track < 64)
return 9;
return 8;
};
uint32_t dataPtr = 0x54;
uint32_t tagPtr = dataPtr + dataSize;
SectorSet sectors;
for (int track = 0; track < numCylinders; track++)
{
int numSectors = sectorsPerTrack(track);
for (int head = 0; head < numHeads; head++)
{
for (int sectorId = 0; sectorId < numSectors; sectorId++)
{
br.seek(dataPtr);
Bytes payload = br.read(512);
dataPtr += 512;
br.seek(tagPtr);
Bytes tag = br.read(12);
tagPtr += 12;
std::unique_ptr<Sector>& sector = sectors.get(track, head, sectorId);
sector.reset(new Sector);
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalTrack = track;
sector->logicalSide = sector->physicalSide = head;
sector->logicalSector = sectorId;
sector->data.writer().append(payload).append(tag);
}
}
}
return sectors;
}
};
std::unique_ptr<ImageReader> ImageReader::createDiskCopyImageReader(
const ImageSpec& spec)
{
return std::unique_ptr<ImageReader>(new DiskCopyImageReader(spec));
}

View File

@@ -5,12 +5,18 @@
#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},
{".diskcopy", ImageReader::createDiskCopyImageReader},
{".img", ImageReader::createImgImageReader},
{".ima", ImageReader::createImgImageReader},
{".jv1", ImageReader::createImgImageReader},
{".jv3", ImageReader::createJv3ImageReader},
};
static bool ends_with(const std::string& value, const std::string& ending)
@@ -42,7 +48,7 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageSpec& spec)
void ImageReader::verifyImageSpec(const ImageSpec& spec)
{
if (!findConstructor(spec))
Error() << "unrecognised image filename extension";
Error() << "unrecognised input image filename extension";
}
ImageReader::ImageReader(const ImageSpec& spec):

View File

@@ -23,7 +23,9 @@ private:
static std::map<std::string, Constructor> formats;
static std::unique_ptr<ImageReader> createDiskCopyImageReader(const ImageSpec& spec);
static std::unique_ptr<ImageReader> createImgImageReader(const ImageSpec& spec);
static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageSpec& spec);
static Constructor findConstructor(const ImageSpec& spec);

View File

@@ -0,0 +1,141 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
#include <fstream>
/* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, which may appear
* in any order, followed by the same again for more sectors. To find the second data block
* you need to know the size of the first data block, which requires parsing it.
*
* https://www.tim-mann.org/trs80/dskspec.html
*
* typedef struct {
* SectorHeader headers1[2901];
* unsigned char writeprot;
* unsigned char data1[];
* SectorHeader headers2[2901];
* unsigned char padding;
* unsigned char data2[];
* } JV3;
*
* typedef struct {
* unsigned char track;
* unsigned char sector;
* unsigned char flags;
* } SectorHeader;
*/
struct SectorHeader
{
uint8_t track;
uint8_t sector;
uint8_t flags;
};
#define JV3_DENSITY 0x80 /* 1=dden, 0=sden */
#define JV3_DAM 0x60 /* data address mark code; see below */
#define JV3_SIDE 0x10 /* 0=side 0, 1=side 1 */
#define JV3_ERROR 0x08 /* 0=ok, 1=CRC error */
#define JV3_NONIBM 0x04 /* 0=normal, 1=short */
#define JV3_SIZE 0x03 /* in used sectors: 0=256,1=128,2=1024,3=512
in free sectors: 0=512,1=1024,2=128,3=256 */
#define JV3_FREE 0xFF /* in track and sector fields of free sectors */
#define JV3_FREEF 0xFC /* in flags field, or'd with size code */
static unsigned getSectorSize(uint8_t flags)
{
if ((flags & JV3_FREEF) == JV3_FREEF)
{
switch (flags & JV3_SIZE)
{
case 0: return 512;
case 1: return 1024;
case 2: return 128;
case 3: return 256;
}
}
else
{
switch (flags & JV3_SIZE)
{
case 0: return 256;
case 1: return 128;
case 2: return 1024;
case 3: return 512;
}
}
Error() << "not reachable";
}
class Jv3ImageReader : public ImageReader
{
public:
Jv3ImageReader(const ImageSpec& spec):
ImageReader(spec)
{}
SectorSet readImage()
{
std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary);
if (!inputFile.is_open())
Error() << "cannot open input file";
inputFile.seekg( 0, std::ios::end);
unsigned inputFileSize = inputFile.tellg();
unsigned headerPtr = 0;
SectorSet sectors;
for (;;)
{
unsigned dataPtr = headerPtr + 2901*3 + 1;
if (dataPtr >= inputFileSize)
break;
for (unsigned i=0; i<2901; i++)
{
SectorHeader header = {0, 0, 0xff};
inputFile.seekg(headerPtr);
inputFile.read((char*) &header, 3);
unsigned sectorSize = getSectorSize(header.flags);
if ((header.flags & JV3_FREEF) != JV3_FREEF)
{
Bytes data(sectorSize);
inputFile.seekg(dataPtr);
inputFile.read((char*) data.begin(), sectorSize);
unsigned head = !!(header.flags & JV3_SIDE);
std::unique_ptr<Sector>& sector = sectors.get(header.track, head, header.sector);
sector.reset(new Sector);
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalTrack = header.track;
sector->logicalSide = sector->physicalSide = head;
sector->logicalSector = header.sector;
sector->data = data;
}
headerPtr += 3;
dataPtr += sectorSize;
}
/* dataPtr is now pointing at the beginning of the next chunk. */
headerPtr = dataPtr;
}
return sectors;
}
};
std::unique_ptr<ImageReader> ImageReader::createJv3ImageReader(
const ImageSpec& spec)
{
return std::unique_ptr<ImageReader>(new Jv3ImageReader(spec));
}

View File

@@ -0,0 +1,168 @@
#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

@@ -13,6 +13,7 @@ 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},
};
@@ -46,7 +47,7 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const SectorSet& sectors, const
void ImageWriter::verifyImageSpec(const ImageSpec& spec)
{
if (!findConstructor(spec))
Error() << "unrecognised image filename extension";
Error() << "unrecognised output image filename extension";
}
ImageWriter::ImageWriter(const SectorSet& sectors, const ImageSpec& spec):

View File

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

View File

@@ -46,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,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"
@@ -107,7 +107,7 @@ void Track::readFluxmap()
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);
@@ -261,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;
}
}

View File

@@ -1,341 +0,0 @@
#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
static libusb_device_handle* device;
static uint8_t buffer[FRAME_SIZE];
static std::string usberror(int i)
{
return libusb_strerror((libusb_error) i);
}
static void usb_init()
{
if (device)
return;
int i = libusb_init(NULL);
if (i < 0)
Error() << "could not start libusb: " << usberror(i);
device = libusb_open_device_with_vid_pid(NULL, FLUXENGINE_VID, FLUXENGINE_PID);
if (!device)
Error() << "cannot find the FluxEngine (is it plugged in?)";
int cfg = -1;
libusb_get_configuration(device, &cfg);
if (cfg != 1)
{
i = libusb_set_configuration(device, 1);
if (i < 0)
Error() << "the FluxEngine would not accept configuration: " << usberror(i);
}
i = libusb_claim_interface(device, 0);
if (i < 0)
Error() << "could not claim interface: " << usberror(i);
int version = usbGetVersion();
if (version != FLUXENGINE_VERSION)
Error() << "your FluxEngine firmware is at version " << version
<< " but the client is for version " << FLUXENGINE_VERSION
<< "; please upgrade";
}
static int usb_cmd_send(void* ptr, int len)
{
//std::cerr << "send:\n";
//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
int i = libusb_interrupt_transfer(device, FLUXENGINE_CMD_OUT_EP,
(uint8_t*) ptr, len, &len, TIMEOUT);
if (i < 0)
Error() << "failed to send command: " << usberror(i);
return len;
}
void usb_cmd_recv(void* ptr, int len)
{
int i = libusb_interrupt_transfer(device, FLUXENGINE_CMD_IN_EP,
(uint8_t*) ptr, len, &len, TIMEOUT);
if (i < 0)
Error() << "failed to receive command reply: " << usberror(i);
//std::cerr << "recv:\n";
//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
}
static void bad_reply(void)
{
struct error_frame* f = (struct error_frame*) buffer;
if (f->f.type != F_FRAME_ERROR)
Error() << fmt::format("bad USB reply 0x{:2x}", f->f.type);
switch (f->error)
{
case F_ERROR_BAD_COMMAND:
Error() << "device did not understand command";
case F_ERROR_UNDERRUN:
Error() << "USB underrun (not enough bandwidth)";
default:
Error() << fmt::format("unknown device error {}", f->error);
}
}
template <typename T>
static T* await_reply(int desired)
{
for (;;)
{
usb_cmd_recv(buffer, sizeof(buffer));
struct any_frame* r = (struct any_frame*) buffer;
if (r->f.type == F_FRAME_DEBUG)
{
std::cout << "dev: " << ((struct debug_frame*)r)->payload << std::endl;
continue;
}
if (r->f.type != desired)
bad_reply();
return (T*) r;
}
}
int usbGetVersion(void)
{
usb_init();
struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY);
return r->version;
}
void usbSeek(int track)
{
usb_init();
struct seek_frame f = {
{ .type = F_FRAME_SEEK_CMD, .size = sizeof(f) },
.track = (uint8_t) track
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_SEEK_REPLY);
}
void usbRecalibrate()
{
usb_init();
struct any_frame f = {
{ .type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f) },
};
usb_cmd_send(&f, f.f.size);
await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY);
}
nanoseconds_t usbGetRotationalPeriod(void)
{
usb_init();
struct any_frame f = { .f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)} };
usb_cmd_send(&f, f.f.size);
auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY);
return r->period_ms * 1000000;
}
static int large_bulk_transfer(int ep, Bytes& bytes)
{
int len;
int i = libusb_bulk_transfer(device, ep, bytes.begin(), bytes.size(), &len, TIMEOUT);
if (i < 0)
Error() << fmt::format("data transfer failed at {} bytes: {}", len, 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);
}

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

@@ -0,0 +1,140 @@
#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);
}
return NULL;
}
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

@@ -47,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,7 +4,7 @@
#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"

View File

@@ -151,9 +151,12 @@ buildlibrary libfmt.a \
dep/fmt/posix.cc \
buildlibrary libbackend.a \
lib/imagereader/diskcopyimagereader.cc \
lib/imagereader/imagereader.cc \
lib/imagereader/imgimagereader.cc \
lib/imagereader/jv3imagereader.cc \
lib/imagewriter/d64imagewriter.cc \
lib/imagewriter/diskcopyimagewriter.cc \
lib/imagewriter/imagewriter.cc \
lib/imagewriter/imgimagewriter.cc \
lib/imagewriter/ldbsimagewriter.cc \
@@ -170,7 +173,11 @@ buildlibrary libbackend.a \
arch/ibm/decoder.cc \
arch/ibm/encoder.cc \
arch/macintosh/decoder.cc \
arch/macintosh/encoder.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 \
@@ -190,6 +197,8 @@ 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/ldbs.cc \
@@ -197,7 +206,6 @@ buildlibrary libbackend.a \
lib/sector.cc \
lib/sectorset.cc \
lib/sql.cc \
lib/usb.cc \
lib/visualiser.cc \
lib/writer.cc \
@@ -207,6 +215,7 @@ buildlibrary libfrontend.a \
src/fe-fluxtoau.cc \
src/fe-fluxtoscp.cc \
src/fe-fluxtovcd.cc \
src/fe-image.cc \
src/fe-inspect.cc \
src/fe-readadfs.cc \
src/fe-readaeslanier.cc \
@@ -220,7 +229,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 \
@@ -232,6 +243,8 @@ buildlibrary libfrontend.a \
src/fe-writeamiga.cc \
src/fe-writebrother.cc \
src/fe-writeibm.cc \
src/fe-writemac.cc \
src/fe-writetids990.cc \
src/fe-writeflux.cc \
src/fe-writetestpattern.cc \
src/fluxengine.cc \

View File

@@ -3,7 +3,7 @@
enum
{
FLUXENGINE_VERSION = 13,
FLUXENGINE_VERSION = 14,
FLUXENGINE_VID = 0x1209,
FLUXENGINE_PID = 0x6e00,

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;
@@ -159,7 +178,7 @@ int mainConvertFluxToScp(int argc, const char* argv[])
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());
}
@@ -167,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())

38
src/fe-image.cc Normal file
View File

@@ -0,0 +1,38 @@
#include "globals.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 <fstream>
static FlagGroup flags { };
static void syntax()
{
std::cout << "Syntax: fluxengine convert image <srcspec> <destspec>\n";
exit(0);
}
int mainConvertImage(int argc, const char* argv[])
{
auto filenames = flags.parseFlagsWithFilenames(argc, argv);
if (filenames.size() != 2)
syntax();
DataSpec ids(filenames[0]);
ImageSpec iis(ids);
SectorSet sectors = ImageReader::create(iis)->readImage();
DataSpec ods(filenames[1]);
ImageSpec ois(ods);
auto writer = ImageWriter::create(sectors, ois);
writer->adjustGeometry();
writer->printMap();
writer->writeImage();
return 0;
}

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

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

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

@@ -9,13 +9,27 @@
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;

24
src/fe-writemac.cc Normal file
View File

@@ -0,0 +1,24 @@
#include "globals.h"
#include "flags.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "macintosh/macintosh.h"
#include "writer.h"
#include "fmt/format.h"
#include <fstream>
static FlagGroup flags { &writerFlags, &macintoshEncoderFlags };
int mainWriteMac(int argc, const char* argv[])
{
setWriterDefaultInput(":c=80:h=2:s=12:b=524");
setWriterDefaultDest(":d=0:t=0-79:s=0-1");
flags.parseFlags(argc, argv);
MacintoshEncoder encoder;
writeDiskCommand(encoder);
return 0;
}

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

@@ -7,6 +7,7 @@ extern command_cb mainConvertCwfToFlux;
extern command_cb mainConvertFluxToAu;
extern command_cb mainConvertFluxToScp;
extern command_cb mainConvertFluxToVcd;
extern command_cb mainConvertImage;
extern command_cb mainConvertScpToFlux;
extern command_cb mainInspect;
extern command_cb mainReadADFS;
@@ -21,7 +22,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 +35,8 @@ extern command_cb mainUpgradeFluxFile;
extern command_cb mainWriteAmiga;
extern command_cb mainWriteBrother;
extern command_cb mainWriteIbm;
extern command_cb mainWriteMac;
extern command_cb mainWriteTiDs990;
extern command_cb mainWriteFlux;
extern command_cb mainWriteTestPattern;
@@ -76,7 +81,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 +93,8 @@ 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.", },
{ "mac", mainWriteMac, "Writes Apple Macintosh disks.", },
{ "tids990", mainWriteTiDs990, "Writes Texas Instruments DS990 disks.", },
};
static std::vector<Command> convertables =
@@ -95,6 +104,7 @@ static std::vector<Command> convertables =
{ "fluxtoau", mainConvertFluxToAu, "Converts (one track of a) flux file to an .au audio file.", },
{ "fluxtoscp", mainConvertFluxToScp, "Converrt a flux file to a Supercard Pro file.", },
{ "fluxtovcd", mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", },
{ "image", mainConvertImage, "Converts one disk image to another.", },
};
static std::vector<Command> testables =

View File

@@ -124,6 +124,13 @@ static void test_tobits()
}));
}
static void test_tostring()
{
std::string s = "Hello, world";
Bytes b(s);
assert(b == s);
}
int main(int argc, const char* argv[])
{
test_bounds();
@@ -133,5 +140,6 @@ int main(int argc, const char* argv[])
test_writes();
test_slice();
test_tobits();
test_tostring();
return 0;
}