Merge remote-tracking branch 'origin/master' into applea2r

This commit is contained in:
Jeff Epler
2022-04-12 10:28:23 -05:00
170 changed files with 4063 additions and 3877 deletions

View File

@@ -1,199 +1,199 @@
:4000000000800020110000009D1000009D100000064A08B5136843F020031360044B1A6803F53F5302331A6001F056F8E8460040FA46004010B5054C237833B9044B13B17D
:400040000448AFF300800123237010BD6881FF1F00000000E8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000021
:400080006C81FF1FE8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1F9C
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080114BDA68196919B9FD
:4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F056B9704700BF60
:400140008881FF1F10B5C4B2204601F087F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002BE5
:400180000CBF02230023002814BF184643F0010002F092FE62B608BD8881FF1F38B50446C5B2284602F0C2F8062002F0DFFA44F00200C0B202F0BAF8062002F0D7FA2846BD
:4001C00002F0B4F8BDE83840062002F0B9BA10B5642402F0A5F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F033F9012805D0204601F04CFAA8
:400200002846FFF79FFF204601F030F9314605460246204601F0ECF9204601F01FF90028FAD1284670BD000038B5044D0024285D013402F04BFA402CF9D138BDAC81FF1F30
:4002400008B502F065FC002002F06EFC02F080FC02F08AFC80B208BD10B50446012002F07DF8642002F06CFAFFF7EAFF2080002002F074F8642002F063FAFFF7E1FF60801C
:4002800010BD08B502F070FD002002F079FD02F08BFD02F095FD80B208BD10B50446FFF796FF322002F04CFAFFF7EBFF20800120FFF774FF322002F043FAFFF7E2FF608072
:4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0F0FE02A802F08CF802F096F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF6278E6
:400300002146BDE81040042001F000B9043A000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0EAFF85
:40034000002002F07DFD002384F8643010BD00BF8881FF1F38B5124D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F8653022790B4B1A71A378002B14BF31
:400380000220012002F05CFDE078B0FA80F0400902F050FD2079BDE8384002F087BD00BF8881FF1FED81FF1F38B50D4C94F8645065B904F16500FFF7CDFF012001F0AAFFDC
:4003C0004FF47A7002F0BCF984F86A50E368E366012384F86430BDE8384002F0EFB900BF8881FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF763FFFFF7E7FECE
:400400000120002384F86A00236702F0AFF92A46216F1848FFF755FF144E0027236F9D4216D001F07DFF00B13767236F9D4205DD0120FFF7B3FE336F013305E005DA002053
:40044000FFF7ACFE336F013B336702F0B7F9E5E7322002F075F92A2DCCBF0020012002F031FDBDE8F8400448FFF72BBF8881FF1F113A0000183A0000353A00002DE9F04F70
:4004800099B062B602F004FA9E49042002F028FA9D4801F051FF9D4802F0F4FC9C4801F085FF02F0D5FB02F0A7FA002002F0C8FC01F0A0FF0221002000F086FF954C012011
:4004C00001F0FEF8002384F86730FFF76DFFFFF77EFE84F87400FFF72BFF012384F86730FFF762FFFFF773FE84F87500FFF720FF894B94F87400894994F875202546002AA4
:4005000014BF0A461A46002808BF19468448FFF7D8FE0321084602F031F9264602F04EF994F8643043B1EA6EEB689B1A41F28832934201D9FFF7FCFE00F07EFF18B9794820
:40054000FFF7BFFE04E000F07DFF0028F7D10BE000F072FF10B902F031F9F9E77248FFF7B0FE032001F098F8032000F077FF0128D4D16E48FFF7EEFE6D490320FFF734FE85
:4000000000800020110000009910000099100000064A08B5136843F020031360044B1A6803F53F5302331A6001F054F8E8460040FA46004010B5054C237833B9044B13B187
:400040000448AFF300800123237010BD6081FF1F00000000E8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000029
:400080006481FF1FE8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8081FF1FAC
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8081FF1F3F000080114BDA68196919B905
:4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F07EB9704700BF38
:400140008081FF1F10B5C4B2204601F085F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002BEF
:400180000CBF02230023002814BF184643F0010002F0D6FB62B608BD8081FF1F38B50446C5B2284602F0C0F8062002F095FD44F00200C0B202F0B8F8062002F08DFD284616
:4001C00002F0B2F8BDE83840062002F06FBD10B5642402F0A3F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F031F9012805D0204601F04AFAF7
:400200002846FFF79FFF204601F02EF9314605460246204601F0EAF9204601F01DF90028FAD1284670BD000038B5044D0024285D013402F001FD402CF9D138BDA481FF1F85
:4002400008B502F0F9F9002002F002FA02F014FA02F01EFA80B208BD10B50446012002F07BF8642002F022FDFFF7EAFF2080002002F072F8642002F019FDFFF7E1FF608067
:4002800010BD08B502F004FB002002F00DFB02F01FFB02F029FB80B208BD10B50446FFF796FF322002F002FDFFF7EBFF20800120FFF774FF322002F0F9FCFFF7E2FF6080B9
:4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0F0FE02A802F0BAF802F0C4F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF62788A
:400300002146BDE81040042001F0FEB8043A000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0E8FF8A
:40034000002002F07FF8002384F8643010BD00BF8081FF1F38B5124D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F8653022790B4B1A71A378002B14BF3C
:400380000220012002F05EF8E078B0FA80F0400902F0D0FA2079BDE8384002F0D7BA00BF8081FF1FE581FF1F38B50D4C94F8645065B904F16500FFF7CDFF012001F0A8FF27
:4003C0004FF47A7002F072FC84F86A50E368E366012384F86430BDE8384002F0A5BC00BF8081FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF763FFFFF7E7FE64
:400400000120002384F86A00236702F065FC2A46216F1848FFF755FF144E0027236F9D4216D001F07BFF00B13767236F9D4205DD0120FFF7B3FE336F013305E005DA00209C
:40044000FFF7ACFE336F013B336702F06DFCE5E7322002F02BFC2A2DCCBF0020012002F07BFABDE8F8400448FFF72BBF8081FF1F113A0000183A0000353A00002DE9F04FBF
:4004800099B062B602F0BAFC9E49042002F0DEFC9D4801F04FFF9D4801F080FF9C4801F0ADFF02F069F902F03BF8002001F0CAFF01F0CEFF0221002000F084FF954C012099
:4004C00001F0FCF8002384F86730FFF76DFFFFF77EFE84F87400FFF72BFF012384F86730FFF762FFFFF773FE84F87500FFF720FF894B94F87400894994F875202546002AA6
:4005000014BF0A461A46002808BF19468448FFF7D8FE0321084602F0E7FB264602F004FC94F8643043B1EA6EEB689B1A41F28832934201D9FFF7FCFE00F07CFF18B97948B1
:40054000FFF7BFFE04E000F07BFF0028F7D10BE000F070FF10B902F0E7FBF9E77248FFF7B0FE032001F096F8032000F075FF0128D4D16E48FFF7EEFE6D490320FFF734FED5
:4005800094F876106B48FFF79CFE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D40333036C
:4005C000D4034D0303238DF820308DF8213010238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD43
:4005C000D4034D0303238DF820308DF8213011238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD42
:400600000B4611E083B10022174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF782FD2F
:400640004FF000080DF1200A02F0B8F84FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF76EFD3A465146022000F04DFFB9F10109EBD108F10108B8F1400F58
:40068000E2D12E4B38E04FF0010A4FF000080DF1200B02F093F84FF0000959460120FFF7A3FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B4641
:400640004FF000080DF1200A02F06EFB4FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF76EFD3A465146022000F04BFFB9F10109EBD108F10108B8F1400FA1
:40068000E2D12E4B38E04FF0010A4FF000080DF1200B02F049FB4FF0000959460120FFF7A3FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B4688
:4006C0004A461F48FFF7FDFD4FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400FD5D151461648FFF7EAFDBAF1000F00F01B81144B1B8807A8ADF81C30AD
:4007000095E200BF55010000F900000091000000C50000008881FF1F473A0000433A00004A3A0000623A0000753A0000ED81FF1FFE81FF1F7F3A0000EC380000EE380000BE
:400740008E3A0000AA3A0000F0380000206FFFF749FE94F8780001F001FE94F8780001F0E5FD02F015FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A789D
:4007800002F0FE021A701A7802F0FE021A7002F003FC0220FFF7D6FC012141F6FF734FF48042084602F052FB84F8B60001F074FF08F807000137402FF8D1DFF8B0A200271A
:4007C0000AF195091FFA89F80137402F14BF3A4600221AF8010F2244062392F82420402101F08EFF424646F24B419AF8000001F099FF08F14008402F1FFA88F8E4D196F859
:40080000793053B196F87C30336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241001F0E9FE94F8B60001F0A7FE01214A
:4008400094F8B60001F07AFE2368002BFCD0002398467360D6F80CA0012701F0AFFFE368B4F87A20CAEB030393420DD367B1042195F8B60001F0D4FE94F8B60001F0E0FEE1
:400880000028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF752FC6968402209EB8111022000F02FFE6A68674B01321340002BBEBF03F1FF3363F091
:4008C0003F03013308F101086360C6E70220277AFFF738FC00221146022000F017FE0220FFF730FCFFB2FFF79FFC002001F01EFD37B15848FFF7E5FC0220FFF709FD06E06E
:40090000554B08A81B88ADF82030FFF7EFFC227D4146237A5148FFF7D4FC15E25048FFF7D0FCD4F87A7017F03F0701D0032009E2286FFFF757FD95F8780001F00FFD95F829
:40094000780001F0F3FC012001F00EFD02F020FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F00FFB01214FF4804315
:4009800041F6FF72084601F0F5FC85F8B60001F083FE08F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F8D7
:4009C0002420402101F09CFE414646F2495299F8000001F0A7FE08F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF060
:4007000095E200BF55010000F900000091000000C50000008081FF1F473A0000433A00004A3A0000623A0000753A0000E581FF1FF681FF1F7F3A0000EC380000EE380000D6
:400740008E3A0000AA3A0000F0380000206FFFF749FE94F8780001F0FFFD94F8780001F0E3FD02F015FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A78A2
:4007800002F0FE021A701A7802F0FE021A7002F003FC0220FFF7D6FC012141F6FF734FF48042084601F0DEFD84F8B60002F02AFA08F807000137402FF8D1DFF8B0A20027DB
:4007C0000AF195091FFA89F80137402F14BF3A4600221AF8010F2244062392F82420402102F044FA424646F24E419AF8000002F04FFA08F14008402F1FFA88F8E4D196F8F2
:40080000793053B196F87C30336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241002F09FF994F8B60002F05DF90121E6
:4008400094F8B60002F030F92368002BFCD0002398467360D6F80CA0012702F065FAE368B4F87A20CAEB030393420DD367B1042195F8B60002F08AF994F8B60002F096F919
:400880000028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF752FC6968402209EB8111022000F02DFE6A68674B01321340002BBEBF03F1FF3363F093
:4008C0003F03013308F101086360C6E70220277AFFF738FC00221146022000F015FE0220FFF730FCFFB2FFF79FFC002001F01CFD37B15848FFF7E5FC0220FFF709FD06E072
:40090000554B08A81B88ADF82030FFF7EFFC227D4146237A5148FFF7D4FC15E25048FFF7D0FCD4F87A7017F03F0701D0032009E2286FFFF757FD95F8780001F00DFD95F82B
:40094000780001F0F1FC012001F098FD02F020FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F00FFB01214FF480438D
:4009800041F6FF72084601F01DFD85F8B60002F039F908F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F8FC
:4009C0002420402102F052F9414646F2484299F8000002F05DF908F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF00D
:400A0000010A4FF0000A2168114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426FD02BB1227A002A7AD12A7D002A77D12068049A059302EB8010BAF1000F36
:400A400016D040223F2102F003FB1CE09B6400403F000080B43A0000F2380000CE3A0000E13A000099650040AC81FF1FAB81FF1F014601370120FFF7B7FBC7EB0903D3F10C
:400A8000000A4AEB030A2168B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F08CFC85F808806B6895F8DC
:400AC000B6002B4493F8241001F09EFD95F8B60001F05CFD012195F8B60001F02FFD95F87E302B6185F81480237D002BFCD04FF00008012086F8148001F076FC404601F0B4
:400B000033FC00E023B1237A5BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F05BFC012001F01EFC002001F05BFC042194F8B60001F072FD94F8B60011
:400B400001F07EFD80460028F8D196F8B60001F00BFD337D327A0293012303920193CDF800A05B463A4649467C48FFF7AAFBC6F81080BAF1000F0BD0FFF756FB002001F028
:400B8000D5FB237A63B17648FFF79BFB0220D9E0B945F1D073490120FFF726FB0137F7E77148FFF78EFB714B3DE094F8780001F0D5FB206FFFF716FC6D48FFF782FB94F8A5
:400BC0007930236100232375237D002BFCD0012001F00AFC00233375237D002BFCD0002001F002FC002363483361FFF76AFB624B19E0002084F86A00FFF7F4FB5F4B12E003
:400C000094F8743023B195F875200AB985F8782094F875201AB113B9012385F878305848FFF798FB574B1B88ADF8203008A8FFF75DFB89E0FFF77CFB02F088F8002002F0FA
:400C40002BF82A2701F056FF002001F0F9FE3A46002108A802F0FCF917238DF820308DF8217001F0ABFD002001F054FB002002F0E7F8C82001F064FD0DF12200FFF7ECFA35
:400C80000DF13600FFF709FB01F098FD012002F0D7F8322001F054FD0DF12600FFF7DCFA0DF13A00FFF7F9FA012001F033FB4FF4967001F045FD01F081FD0DF12E00FFF7AC
:400CC000CBFA0DF14200FFF7E8FA002001F022FB4FF4967001F034FD01F070FD022002F0AFF8322001F02CFD0DEB0700FFF7B4FA0DF13E00FFF7D1FA012001F00BFB4FF4FE
:400D0000967001F01DFD01F059FD0DF13200FFF7A3FA0DF14600FFF7C0FA002001F0FAFA4FF4967001F00CFD01F048FD002002F087F8002384F86A3001F08AFF01F05CFE97
:400D400074E70120FFF7E4FA032000F0A5FC0E48FFF7B7FAFFF7E2BB3F000080EB3A00001B3B00004092FF1F253B0000F43800002D3B00003B3B0000F6380000F8380000ED
:400D8000FE81FF1FFA380000483B00000F4B1A78120616D55878C0B2012814D11B79DBB2042B02D0052B05D07047094A094B5A60282203E0084A074B5A60E0221A8000F04C
:400DC00045BE002070470120704700BF00600040FC380000C492FF1F243900002DE9F04172B68A4B61221A70A3F5F06301221A801924874A9C7092E803008033062283F8F7
:400E0000002283E80300522203F580731A70814B814A1B78814EDBB2137040F61802804B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0A1F8013CE3
:400E400005F003052ED0032DF0D1764B4FF480721A8007221A70744A002548211570917002221D705D7103F8032C0422DA716F4A6F4C13786F4E43F00103137012F8013C9A
:400E8000062743F0030302F8013C2378012243F0800323705D4B1A70674A137843F02003137000E0FEE707FB056300219A881868013502F0CDF8072DF5D16048604E0025F3
:400EC00050F8041F05F1105303F14A0221F0FF074B33C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1534A00231360936013619361FD
:400F0000514B524F1A68524BDFF88C811A60514B1A6803F1784303F5D6431A604E4A137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F047
:400F400010031370454A464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A803F4A083307CA03C31A803E4A3E4BA2F5B0
:400F8000616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030326
:400FC00088F800302E4B48221A702E4A402313702D49937013729372082382F81F3220220A7048710A72294A0A20137001F0BEFB274B88F8006044223D70264D1A7094E8E4
:401000000F0007C52B80BDE8F08100BF004800401C0B00480F010049A146004025420040224200400440004006400040A2430040A04300404D3B0000E8460040FCFFFF470A
:401040009000004800760040240B0048F846004020760040280B004803500140DC0A0048C0510040E80A0048F00A0048FC0A0048080B004832510040140B0048CF01004921
:401080001D51004001590040235B0040585B004076580040B0430040F946004008B501F0ABFF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E043
:4010C00001F09AFF0C2303604FF0FF33184608BDCC80FF1F9093FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F88D
:40110000046C8C1AA64202D041F8045BF9E701381033E5E701F076FFFFF7B0F9FEE700BF01000000E03C0000124A134B10B51A60124A134C1368134843F40073136000238F
:40114000032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF739FEBDE8104001F0CFB900BF0004FA050CED00E014ED00E016
:40118000000000000080FF1F9D100000BC760040C080FF1F08ED00E0F8B501F0F9FE4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3215
:4011C00003F8946C1378054603F07F031370002001F0CCFA2378404A03F0F90323701378384603F0DF03137023783B43237001F0BDFA282001F0BAFA384B30461A7802F089
:401200007F021A701A7802F0BF021A7023783343237001F0ABFA2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0B0FE0721172001F0DEFA5F
:401240002949172001F0CCFA0721182001F0D6FA2649182001F0C4FA0721152001F0CEFA2349152001F0BCFA0721052001F0C6FA2049052001F0B4FA0721062001F0BEFA17
:401280001D49062001F0ACFA0721084601F0B6FA1A49072001F0A4FA0721082001F0AEFA1749082001F09CFA0021162001F0A6FA1449162001F094FA07210C2001F09EFA0C
:4012C000BDE8F84010490C2001F08ABAA5430040944300409D60004012600040F851004084600040B592FF1F671B0000A1190000651B0000991A0000C51A0000F51A0000BA
:401300002D1B00006D1B0000E11B0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080729F
:401340001A604FF400121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70D2
:40138000802203F8202C012001F0FAFD0D4B04221A7010BDD092FF1FD692FF1FD492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F00E100E09E6000409C600040AF
:4013C000286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF8092FF1F0A4A002313700A4A13700A4A13700A4A13700A4ADE
:4014000013700A4A13700A4A13700A4B03221A70802203F8202C7047D692FF1FD492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F28600040014B1878704700BFC1
:40144000D592FF1F044B1A7802F0FF001AB118780022C0B21A707047D492FF1F024A0C2303FB002040787047DC92FF1F431E072B0CD8074A064B00010344805C5B7800F0CD
:401480000F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE8AD
:4014C00005F002070C110021084600F0FDFF0DE00021084600F0DCFF08E00021084600F0BBFF03E00021084600F09AFF054B1855EDB2072D03D801F0CFF8034B185538BDA0
:40150000DC92FF1FAC92FF1FB592FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7824
:4015400084BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D00F016
:40158000F3FF11232946FE2218F8040001F0B8F806F5C04278321FFA89F118F8040001F0C1F8124D18F80410385D01F02DF80121385D00F0C3FF735D43F002037355735D92
:4015C00003F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFDC92FF1FFC5F0040B592FF1FAC92FF1F706000402DE9F047044615468846002940D0431E54
:40160000072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040000F09BFF1223FE222946305D01F061F807F557
:40164000C0411FFA88F27831305D01F06BF8DFF84490315D1AF8040000F0D6FF01211AF8040000F06BFF17F8093043F0020307F8093017F8093003F0FD0307F8093002E099
:401680000D4600E000252846BDE8F087B592FF1FAC92FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFDC92FF1FFE5F0040C3
:4016C000431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E854506895
:401700000133013050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF12
:401740004C93FF1F4893FF1F00600040C492FF1FC192FF1FD692FF1FD292FF1F4993FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047C2
:40178000D692FF1FD292FF1FC192FF1F4893FF1F4993FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013B85
:4017C000C9B229705168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404C93FF1F00600040C492FF1F4C
:401800004993FF1FD292FF1FC192FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFD692FF1FC192FF1FD292FF1F4993FF1F054B9A683AB19A682B
:40184000044910709A680988518000229A607047C492FF1F4C93FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202BFD
:4018800005D0402B06D043B900F012FC04E001F087FB01E0FFF77AFA10B9034B03221A7008BD00BF28600040C192FF1F0060004008B5084A084B0120197813880B449BB208
:4018C0001380064B00221A70FFF7B6FF044B03221A7008BD4C93FF1F4893FF1FD692FF1FC192FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BF34
:40190000BDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFD692FF1FC192FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFD692FF1FC192FF1FCE
:4019400008B50A4B1A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFC092FF1F08600040D692FF1FC192FF1F074B1B782C
:40198000DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047D692FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B782A
:4019C000190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB200064D
:401A000005D41378DBB20B700B7803F00F0328788342F1D138BD38BD28600040C192FF1FD292FF1F4993FF1F29600040054A00231380054A916819B191680B7092685380CB
:401A4000704700BF4C93FF1FC492FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF03806C
:401A8000FFF728FE012008BDC492FF1FD692FF1FD292FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040D9
:401AC000DC92FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040DC92FF1F0B4B04221A700F3B93F83230CF
:401B0000094B93F8242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040DC92FF1F0B4B08221A700F3B93F84230094B93F8302002F0F9
:401B40000302012A1EBF93F8332082F0800283F83320012283F83120704700BF0B600040DC92FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B372
:401B8000CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF05
:401BC00084F801C090700133DBB24908D7E7F0BD9F600040DC92FF1F70600040FE5F004000F08EBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46B4
:401C0000F7D11B782B70FCB12223237001AD03232846637000F06CFE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC170037125
:401C4000417100F10400EAD108B070BD773B00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F62
:401C800007D098F8044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF2213
:401CC0001A70BDE8F08300BFDC92FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047D092FF1F9C3B00002DE9F84F64
:401D0000424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891
:401D400091425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F11C
:401D8000010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF330
:401DC000072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F3072281
:401E00004A71083394E7BDE8F88F00BFD592FF1FDC92FF1FD192FF1FFC5F004070600040C292FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDDA
:401E4000D592FF1F00212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002AC0
:401E80007FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB020993
:401EC0004B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F12
:401F000006F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F814
:401F4000081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF732
:401F800067BEBDE8F88F00BFDC92FF1FC292FF1F4A93FF1FD592FF1FD392FF1FD892FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F00101917098
:401FC0000021D170517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040DC92FF1FFC5F004030B4194B1A7902F07F02531EE1
:40200000072B27D8164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E077
:402040000D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040DC92FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B791B
:402080008B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDD592FF1F00600040C292FF1F4A93FF1F38B5874A874C13780021DBB221801806BC
:4020C000517840F188800A2900F20081DFE811F05800FE00FE00FE00FE00FE000B00FE007900FE007D00D3787949012B09D17A4B1A787A4B03EBC2035B685B6863601223C5
:4021000010E0CB78022B12D18878FFF7E5FD002800F0DC80436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B21D16A4B00228878D5B2854203D3F6
:40214000634A92783AB910E0187801320028F7D018780344F0E75E4A62499278097C914203D16148FFF73EFD5F4B1A78002A00F0AD801A78228018E0BDE8384000F010BF77
:4021800013F0030313D0022B40F0A0802380504B0C211B7903F07F02544B01FB02339A78534BD2B21A7000225A706360BBE702222280504A11784E4AC9B211705370626062
:4021C000B1E7012323804C4BEFE70123238013794A4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378444ADBB25AE0937803F0FF0153B93E4B1A7892
:4022000091425FD019703F4B01201870FFF71AFE58E0481EC0B2FFF75FFD0028EED155E0FFF722FF002851D0294A374913791279DBB2D2B20A70354A3049D25CCB5C9A4236
:4022400040D0304B01221A70FFF758FD3AE003F00303012B2BD009D3022B37D11C4B9B78002B33D1BDE83840FFF7C4BE184B9B78012B2BD11F4A137803F0FD0315E003F0E5
:402280000303012B13D008D3022B1FD1104B9B78E3B9BDE83840FFF783BE0D4B9B78012B14D1144A137843F0020313700AE0084B1A795AB998781B791549DBB2CA5C22EAEF
:4022C0000002CA54BDE83840FFF7A0BA002038BD00600040C492FF1FD092FF1F9C3B0000003C0000733C00006893FF1FDC92FF1F8192FF1FD392FF1FD592FF1FC292FF1F96
:40230000C092FF1FD492FF1FD192FF1F4A93FF1FD792FF1F014B1870704700BF78650040014B1878704700BF6C640040014B1870704700BF78640040064A0123136002F698
:4023400088321268E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF7A650040014B1870704700BF7F64004073B515461E460B4C0523002210
:40238000019200920A4601461846237000F064F932462946207800F01FF90221207800F009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A117031
:4023C000A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BF7E640040704738B505460078012428B1CB
:4024000000F066FD285D0134E4B2F8E738BD08B50D2000F05DFDBDE808400A2000F058BDF7B516461F460B4C00230325019300930A4601462846257000F00EF93A46314617
:40244000207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F0F2F83A463146207800F0ADF829460B
:40248000207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207844
:4024C00003B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1FB2
:40250000024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD877
:40254000084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25490
:4025800000207047012070471070004017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C4900D2
:4025C00003F0F10301F00E011943815400207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC28072
:40260000002001207047000017289FBF034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF147000408D
:4026400017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA021304
:40268000DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F079FC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F0DE
:4026C0006BFC204610BD00BFE480FF1F030610B5044611D400F05CFC084AE300117803F1804303F5F04319705378147001335370BDE8104000F050BC10BD00BFE480FF1F4C
:4027000030B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC28000200120704700000C
:4027400038B50446084DB4F5004F05D9286800F017FCA4F50044F6E7034B58686043BDE8384000F00DBC00BFEC80FF1F024B1B7A584300F005BC00BFEC80FF1F0E4B00F01A
:4027800003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B012277
:4027C0001A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0089
:4028000000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A9B
:402840001360084B4FF400421C60C3F8E82010BD8492FF1FBD28000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034AF5
:40288000136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF8492FF1F024B1868C0F30040704700BF10E000E038B5FFF723
:4028C000F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF8892FF1F024B03EB80035868596070478492FF1F134B144A1B78DBB20360127843EA0223E0
:40290000114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF30
:402940000301004904010049EC460040020100490101004900010049050100490601004910B500F015FB204A044613780A2043F002031370137C43F00203137412F80A3C43
:4029800043F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CBB
:4029C000A3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D8BAAB4300400E5900402F5B004080E200E008B500F0C9FA0F4A73
:402A0000137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0AFBA00BF0859004072
:402A4000044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F9101067
:402A80000A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470A590040883B00005293FF1F5493FF1F5893FF1F08B5102000F0A6F9C9
:402AC00007210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BDB12B0000095900405093FF1F10B5054C23781BB9FFF7DCFFF1
:402B000001232370BDE81040FFF72ABFA092FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7D9
:402B400015FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF5893FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B98
:402B80001B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475293FF1F5493FF1F5093FF1F7047000010B500F0EBF9214A0446137884
:402BC0000A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400241
:402C000003F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0ADB900BFEB
:402C4000AB43004006590040275B004080E200E008B500F09DF90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F8BA
:402C80000A3C937903F0FD039371BDE8084000F083B900BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E04223BE
:402CC00008380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470259004094
:402D0000923B00005E93FF1F6493FF1F5C93FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BD88
:402D4000092E0000015900406093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BFA192FF1F044B1A7802F0FB021A701A7842F001021A7070470059004046
:402D800010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5C93FF1FA05B00406B
:402DC0000E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475E93FF1F0D
:402E00006493FF1F6093FF1F70470000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040014B1870704700BF7C64004043
:402E4000014B1870704700BF7B64004073B515461E460B4C04230022019200920A46014618462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDE9
:402E8000FC80FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF74640040014B1870704700BFE2
:402EC00075640040014B1870704700BF7D640040FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF814388346CB
:400A400016D040223F2102F003FB1CE09E6400403F000080B43A0000F2380000CE3A0000E13A000098640040A481FF1FA381FF1F014601370120FFF7B7FBC7EB0903D3F11B
:400A8000000A4AEB030A2168B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F0B4FC85F808806B6895F8B4
:400AC000B6002B4493F8241002F054F895F8B60002F012F8012195F8B60001F0E5FF95F87E302B6185F81480237D002BFCD04FF00008012086F8148001F09EFC404601F070
:400B0000BDFC00E023B1237A5BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F083FC012001F0A8FC002001F083FC042194F8B60002F028F894F8B600FB
:400B400002F034F880460028F8D196F8B60001F0C1FF337D327A0293012303920193CDF800A05B463A4649467C48FFF7AAFBC6F81080BAF1000F0BD0FFF756FB002001F0BE
:400B8000D3FB237A63B17648FFF79BFB0220D9E0B945F1D073490120FFF726FB0137F7E77148FFF78EFB714B3DE094F8780001F0D3FB206FFFF716FC6D48FFF782FB94F8A9
:400BC0007930236100232375237D002BFCD0012001F032FC00233375237D002BFCD0002001F02AFC002363483361FFF76AFB624B19E0002084F86A00FFF7F4FB5F4B12E0B3
:400C000094F8743023B195F875200AB985F8782094F875201AB113B9012385F878305848FFF798FB574B1B88ADF8203008A8FFF75DFB89E0FFF77CFB01F01CFE002001F062
:400C4000BFFD2A2701F0EAFC002001F08DFC3A46002108A802F0FCF917238DF820308DF8217002F061F8002001F052FB002001F0E9FBC82002F01AF80DF12200FFF7ECFA13
:400C80000DF13600FFF709FB02F04EF8012001F0D9FB322002F00AF80DF12600FFF7DCFA0DF13A00FFF7F9FA012001F031FB4FF4967001F0FBFF02F037F80DF12E00FFF7DC
:400CC000CBFA0DF14200FFF7E8FA002001F020FB4FF4967001F0EAFF02F026F8022001F0B1FB322001F0E2FF0DEB0700FFF7B4FA0DF13E00FFF7D1FA012001F009FB4FF4DC
:400D0000967001F0D3FF02F00FF80DF13200FFF7A3FA0DF14600FFF7C0FA002001F0F8FA4FF4967001F0C2FF01F0FEFF002001F089FB002384F86A3001F01EFD01F0F0FB98
:400D400074E70120FFF7E4FA032000F0A3FC0E48FFF7B7FAFFF7E2BB3F000080EB3A00001B3B00003892FF1F253B0000F43800002D3B00003B3B0000F6380000F8380000F7
:400D8000F681FF1FFA380000483B00000F4B1A78120616D55878C0B2012814D11B79DBB2042B02D0052B05D07047094A094B5A60282203E0084A074B5A60E0221A8000F054
:400DC00043BE002070470120704700BF00600040FC380000BC92FF1F243900002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F805
:400E0000002283E80300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0A1F8013CEB
:400E400005F003052ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013CA4
:400E8000062743F0030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0CDF8072DF5D15E485E4E0025FB
:400EC00050F8041F05F1105303F14A0221F0FF074B33C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A00231360936013619361FF
:400F00004F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A52
:400F4000464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F888
:400F8000100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B47
:400FC00048221A702E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F077FE284B88F8006044223D70264D1A7094E80F0007C52B80C9
:40100000BDE8F08100480040680A00480F010049A146004025420040224200400440004006400040A2430040A04300404D3B0000E8460040FCFFFF47900000480076004076
:40104000700A0048F846004020760040740A00482876004003500140280A0048C0510040340A00483C0A0048480A0048540A004832510040600A0048CF0100491D510040C7
:4010800001590040235B0040585B004076580040B0430040F946004008B501F0ADFF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F09CFF63
:4010C0000C2303604FF0FF33184608BDCC80FF1F8893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1A09
:40110000A64202D041F8045BF9E701381033E5E701F078FFFFF7B2F9FEE700BF01000000E03C0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF1C
:4011400054F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F087BC00BF0004FA050CED00E014ED00E000000000DE
:401180000080FF1F99100000BC760040C080FF1F08ED00E0F8B501F0FBFE4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C1C
:4011C0001378054603F07F031370002001F084FD2378404A03F0F90323701378384603F0DF03137023783B43237001F075FD282001F072FD384B30461A7802F07F021A7048
:401200001A7802F0BF021A7023783343237001F063FD2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0B2FE0721172001F096FD2949172049
:4012400001F084FD0721182001F08EFD2649182001F07CFD0721152001F086FD2349152001F074FD0721052001F07EFD2049052001F06CFD0721062001F076FD1D4906205C
:4012800001F064FD0721084601F06EFD1A49072001F05CFD0721082001F066FD1749082001F054FD0021162001F05EFD1449162001F04CFD07210C2001F056FDBDE8F840E3
:4012C00010490C2001F042BDA5430040944300409D60004012600040F851004084600040AD92FF1F631B00009D190000611B0000951A0000C11A0000F11A0000291B0000B8
:40130000691B0000DD1B0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF432
:4013400000121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8F2
:40138000202C012001F0FCFD0D4B04221A7010BDC892FF1FCE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F00E100E09E6000409C60004028600040C2
:4013C0001260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF7892FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4AD7
:4014000013700A4A13700A4B03221A70802203F8202C7047CE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F28600040014B1878704700BFCD92FF1F53
:40144000044B1A7802F0FF001AB118780022C0B21A707047CC92FF1F024A0C2303FB002040787047D492FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA26
:401480000020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F00207EB
:4014C0000C110021084601F0A1FA0DE00021084601F080FA08E00021084601F05FFA03E00021084601F03EFA054B1855EDB2072D03D801F087FB034B185538BDD492FF1FDF
:40150000A492FF1FAD92FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F5C3
:401540000075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F0ABFA112339
:401580002946FE2218F8040001F070FB06F5C04278321FFA89F118F8040001F079FB124D18F80410385D01F0E5FA0121385D01F07BFA735D43F002037355735D03F0FD03E1
:4015C0007355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFD492FF1FFC5F0040AD92FF1FA492FF1F706000402DE9F047044615468846002940D0431E072B3FD816
:40160000FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040001F053FA1223FE222946305D01F019FB07F5C0411FFA17
:4016400088F27831305D01F023FBDFF84490315D1AF8040001F08EFA01211AF8040001F023FA17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E05D
:4016800000252846BDE8F087AD92FF1FA492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFD492FF1FFE5F0040431E072B7B
:4016C0009FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E854506801330130C3
:4017000050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF4493FF1F82
:401740004093FF1F00600040BC92FF1FB992FF1FCE92FF1FCA92FF1F4193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047CE92FF1F71
:40178000CA92FF1FB992FF1F4093FF1F4193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B2297017
:4017C0005168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404493FF1F00600040BC92FF1F4193FF1F7E
:40180000CA92FF1FB992FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFCE92FF1FB992FF1FCA92FF1F4193FF1F054B9A683AB19A680449107088
:401840009A680988518000229A607047BC92FF1F4493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B9A
:4018800006D043B900F012FC04E001F089FB01E0FFF77CFA10B9034B03221A7008BD00BF28600040B992FF1F0060004008B5084A084B0120197813880B449BB21380064B68
:4018C00000221A70FFF7B6FF044B03221A7008BD4493FF1F4093FF1FCE92FF1FB992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE808404B
:40190000FFF746BF0320FFF795FF034B03221A7008BD00BFCE92FF1FB992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFCE92FF1FB992FF1F08B50A4BC9
:401940001A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFB892FF1F08600040CE92FF1FB992FF1F074B1B78DBB2042B9A
:4019800005D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047CE92FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D5F1
:4019C00038BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378E6
:401A0000DBB20B700B7803F00F0328788342F1D138BD38BD28600040B992FF1FCA92FF1F4193FF1F29600040054A00231380054A916819B191680B7092685380704700BFD1
:401A40004493FF1FBC92FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FED6
:401A8000012008BDBC92FF1FCE92FF1FCA92FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040D492FF1F89
:401AC000094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040D492FF1F0B4B04221A700F3B93F83230094B93F884
:401B0000242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040D492FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012AB0
:401B40001EBF93F8332082F0800283F83320012283F83120704700BF0B600040D492FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5E4
:401B8000144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C08E
:401BC00090700133DBB24908D7E7F0BD9F600040D492FF1F70600040FE5F004000F032BF70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B78F7
:401C00002B70FCB12223237001AD03232846637001F024F9002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F129
:401C40000400EAD108B070BD773B00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F89E
:401C8000044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE84B
:401CC000F08300BFD492FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047C892FF1F9C3B00002DE9F84F424B1A7884
:401D0000002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED1AE
:401D40009A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF43
:401D80004FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F861
:401DC00000A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083335
:401E000094E7BDE8F88F00BFCD92FF1FD492FF1FC992FF1FFC5F004070600040BA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDCD92FF1F73
:401E400000212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187866
:401E800001250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D0E1
:401EC00093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F00306A4
:401F000007DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F8E4
:401F40000A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE897
:401F8000F88F00BFD492FF1FBA92FF1F4293FF1FCD92FF1FCB92FF1FD092FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D17030
:401FC000517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040D492FF1FFC5F004030B4194B1A7902F07F02531E072B27D81A
:40200000164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E095
:40204000012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040D492FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D296
:40208000012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDCD92FF1F00600040BA92FF1F4293FF1F38B5874A874C13780021DBB221801806517840F17A
:4020C00088800A2900F20081DFE811F05800FE00FE00FE00FE00FE000B00FE007900FE007D00D3787949012B09D17A4B1A787A4B03EBC2035B685B686360122310E0CB788C
:40210000022B12D18878FFF7E5FD002800F0DC80436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B21D16A4B00228878D5B2854203D3634A927872
:402140003AB910E0187801320028F7D018780344F0E75E4A62499278097C914203D16148FFF73EFD5F4B1A78002A00F0AD801A78228018E0BDE8384000F012BF13F0030323
:4021800013D0022B40F0A0802380504B0C211B7903F07F02544B01FB02339A78534BD2B21A7000225A706360BBE702222280504A11784E4AC9B2117053706260B1E70123AF
:4021C00023804C4BEFE70123238013794A4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378444ADBB25AE0937803F0FF0153B93E4B1A7891425FD04C
:4022000019703F4B01201870FFF71AFE58E0481EC0B2FFF75FFD0028EED155E0FFF722FF002851D0294A374913791279DBB2D2B20A70354A3049D25CCB5C9A4240D0304BAD
:4022400001221A70FFF758FD3AE003F00303012B2BD009D3022B37D11C4B9B78002B33D1BDE83840FFF7C4BE184B9B78012B2BD11F4A137803F0FD0315E003F00303012B3E
:4022800013D008D3022B1FD1104B9B78E3B9BDE83840FFF783BE0D4B9B78012B14D1144A137843F0020313700AE0084B1A795AB998781B791549DBB2CA5C22EA0002CA5401
:4022C000BDE83840FFF7A0BA002038BD00600040BC92FF1FC892FF1F9C3B0000003C0000733C00006093FF1FD492FF1F7992FF1FCB92FF1FCD92FF1FBA92FF1FB892FF1F8E
:40230000CC92FF1FC992FF1F4293FF1FCF92FF1F014B1870704700BF72640040014B1878704700BF68650040014B1870704700BF7A650040064A0123136002F688321268FB
:40234000E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF7A64004073B515461E460B4C04230022019200920A4601461846237000F022FCF8
:4023800032462946207800F0DDFB0221207800F0C7FB207802B070BDD080FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E05F
:4023C00001E400E073B515461E460B4C05230022019200920A4601461846237000F0F2FB32462946207800F0ADFB0221207800F097FB207802B070BDD180FF1F064A042355
:40240000136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BFAF
:402440007B650040014B1870704700BF73640040704738B505460078012428B100F038FD285D0134E4B2F8E738BD08B50D2000F02FFDBDE808400A2000F02ABD014B187065
:40248000704700BF7865004010B500F081FD204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52137843F024
:4024C00003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F00803137000F0ECFBF7
:40250000064B10222046BDE810401A6000F044BDAB4300400E5900402F5B004080E200E008B500F035FD0F4A137803F0FE031370A2F5AA521D3A137803F0FD031370137CBD
:4025400003F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F01BBD00BF08590040044A137803F03F0343EA8010C0B21070704700BF0859004070
:40258000082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBE4
:4025C000F1F305490B60054B1A8070470A590040883B00004A93FF1F4C93FF1F5093FF1F08B5102000F036FA0721042000F0BCFB0749042000F0AAFB064A0C20137843F0FB
:4026000006031370FFF7BCFF034B00221A8008BDD9260000095900404893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABF7B92FF1F044B1A7802F0FB0218
:402640001A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E0204600F037FB024B1B78204610BD00BF09590040034A044B1B881088D2
:40268000181A00B2704700BF5093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF97
:4026C0005B42134493FBF1F000B270474A93FF1F4C93FF1F4893FF1F7047000010B500F057FC214A044613780A2043F001031370137C43F00103137412F80A3C43F0020365
:4027000002F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F591
:4027400097530222123B1A70094A137843F00803137000F0C1FA074B08222046BDE810401A6000F019BC00BFAB43004006590040275B004080E200E008B500F009FC0F4A79
:40278000137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F0EFBB00BF00590040C4
:4027C000044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F91010F2
:402800000A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A80704702590040923B00005693FF1F5C93FF1F5493FF1F08B5102000F014F9D3
:402840000721032000F090FA0749032000F07EFA064A0C20137843F006031370FFF7BCFF034B00221A8008BD31290000015900405893FF1F10B5054C23781BB9FFF7DCFF1F
:4028800001232370BDE81040FFF728BF7C92FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0002404E0204600F090
:4028C0000BFA024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5493FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B37
:402900001B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475693FF1F5C93FF1F5893FF1F70470000014B1870704700BF7B64004062
:40294000014B1870704700BF7F640040014B1870704700BF7C640040014B1870704700BF7E640040F7B516461F460B4C00230325019300930A4601462846257000F022F939
:402980003A463146207800F0DDF80221207800F0C7F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F006F93A463146207800F0A6
:4029C000C1F82946207800F0ABF8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0EAF83A463146207800F0A5F80221207800F0A6
:402A00008FF8207803B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0CFF832462946207800F08AF80221207800F074F8207802B070BD00BF92
:402A4000E380FF1F024B1878C0F38010704700BF8F450040034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040074A7F2330
:402A8000802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200201E002F0DF02C2543D
:402AC000C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004017280DD80749000179
:402B00000B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E0119438154002070470120704769
:402B40001070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF034B00011954002042
:402B800088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F0070088BFFF20704705
:402BC00014700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD6D
:402C0000012010BD10B500F0C3F90A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F0B5F9204610BD00BFE480FF1F030610B5044611D478
:402C400000F0A6F9084AE300117803F1804303F5F04319705378147001335370BDE8104000F09AB910BD00BFE480FF1F30B504060CD411F4704509D1C40004F1804404F537
:402C8000F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D9286800F061F9A4F58B
:402CC0000044F6E7034B58686043BDE8384000F057B900BFEC80FF1F024B1B7A584300F04FB900BFEC80FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600107
:402D000042F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F00F021B6853F82200F7
:402D400043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B555
:402D80000F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD8092FF1FC5
:402DC000292E000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054CFA
:402E0000A3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF8092FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F8243003B1984701345B
:402E4000052CF8D138BD00BF8492FF1F024B03EB80035868596070478092FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A0360127843EA0263A4
:402E800003600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC46004002010049010100499A
:402EC00000010049050100490601004900000000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF81438834666
:402F00000121C9430C460125002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D900200130013812
:402F4000013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C2010000000000FD
:402F80000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A4A
:402FC00000E013790020704700600040DC92FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BFEC
:403000006081FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E700F044FE084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD98
:402FC00000E013790020704700600040D492FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BFF4
:403000005C81FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E700F044FE084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD9C
:40304000B83C0000B83C0000B83C0000C03C000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBFA2
:40308000234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A464B
:4030C000014603480068FFF7CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F5B9
:4030C000014603480068FFF7CBFF03B05DF804FB5C81FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F5BD
:403100007B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FAA0
:403140000C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E44F5
:403180002660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF86F
@@ -220,12 +220,12 @@
:4036C000002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD71
:40370000994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A0188342ED
:4037400001BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232A
:403780002B6009E021686018824201BF106852680918216062605C602846BDE8384000F098B838BDA892FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA9429D
:403780002B6009E021686018824201BF106852680918216062605C602846BDE8384000F098B838BDA092FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA942A5
:4037C00002D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E70D
:40380000174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD37
:40384000211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA892FF1FA492FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F5
:40388000F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7F6FB46
:4038C000431C02D1236803B12B6038BD8C93FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F021102130215022800000013
:40384000211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA092FF1F9C92FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF284605
:40388000F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7F4FB48
:4038C000431C02D1236803B12B6038BD8493FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F02110213021502280000001B
:40390000000104000100000000000000000157494E55534200003030303031000000000000000000E0000000000105000100D6000000070000002A00440065007600690041
:403940006300650049006E0074006500720066006100630065004700550049004400730000009E0000007B00330064003200370035006300660065002D003500340033000D
:4039800035002D0034006400640035002D0061006300630061002D003900660062003900390035006500320066003600330038007D0000007B00330064003200370035001F
@@ -240,10 +240,10 @@
:403BC000453C000004000000DE3B0000000000000000000000000000DC3B0000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF000203FC
:403C000004030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F006C006F006700690036
:403C4000650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000A0705840340000A12010002FF0001080912006E016F
:403C800000020180014300232D302B2000686C4C00656667454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000029110000DA
:403CC000F8B500BFF8BC08BC9E46704735000000E03C0000C880FF1FA000000028120000000000009093FF1FFF000000675000400C00000007000000FFFFFFFF7F8000006F
:403D00003F0000000000007D00FA0000400000000090D003FF0000000000000000000000000000000000000000000000000000000000000000000000853C0000000000006A
:403D400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081FF1F00000000A4
:403C800000020180014300232D302B2000686C4C00656667454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000025110000DE
:403CC000F8B500BFF8BC08BC9E46704735000000E03C0000C880FF1F9800000028120000000000008893FF1FFFFF0000675000400C00000007000000FFFFFFFF7F80000080
:403D00003F0000000000007D00FA0000400000000090D0030000000000000000000000000000000000000000000000000000000000000000853C0000000000000000000069
:403D400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FC80FF1F0000000000000000A9
:403D80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
:403DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C3
:403E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082
@@ -4098,51 +4098,51 @@
:40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041
:40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
:0200000480007A
:400000000145004008520040015B004001640040020101400B0301403E04014041050140130701405508014056090140510A0140540B01405B0C0140540D0140470E01400D
:40004000380F01400A1501400B1701404B18014059190140521A01404E1B01400B40014012410140144201400C430140044401400D4501400946014011470140084801406A
:4000800011490140154C0140034D014006500140045101407E0208020941118218021901600C61067C4027212D0A8C40E62082608580908091029840AD01B040E008E224DC
:4000C000E680EE020008021003020405052006020B080F041110120117041A021B011C041F0420062107220123082610270428102A082B042F04300731203208330F361052
:40010000371038883A303B083E453F4156085808590A5B045C995D905F0189018B028D039601A002A102A301AC01B202B303B601BE04D608D804D904DB04DC99DD90DF0157
:40014000000A09900A400F10100811081302164119801AA01B221C101F802142231026082A252B012F80300231443220388039023B143F90584059105F80618063046640B5
:4001800068046B026D206E407A107B028110822088088A108E018F4090809102968098409A209D80A502B502C00CC22BC49ECA8FCC0FCE3FD61CD81CDE0AE204E60DEA04B2
:4001C000EE0181108D449010918092609D449E049F80A110A402A701A904AC01B208B720E202E60AEA05EE080004020804010602080409010AF90B140D010E020F2810030B
:4002000012FC134114041580164017011A041B4E1C801E042202234124042620274128042A102B412CFF2DF12F02310F32FF330F35F03B0A3E045804590B5B045C905F0188
:40024000800481078378840485088730880489808C038DFF8E0490019101920294029502970498029C029DA09F48A002A102A304A404A53FA802A908AB10AC05AF80B13F30
:40028000B207B33FB7C0BB80BE04D804D904DC90DF01008801020308048405020708084009220A110C040D090F0911011202132814801568170119041C202104224A230182
:4002C00025052714288629042C022F6430083102324035013714394A3B203F60422043045C405D206D106E407C028D01914293689420952D96909785980A99039A089B0820
:400300009C709D109E069FD5A042A108A280A31CA494A502A65DA702AB02AD10B220B380B610C0FFC2FFC4FFCAFFCCEBCE3FD630DE80E212E410E60AE840EA01EC80000C29
:4003400002F1040C06100A040E021080120C140C164018011A021C031EFC22022608280C2A202CFF34FF3E10560858045B045D905F0180088121830284C085218601870427
:4003800088088B108D2F8E04900291019204941A96209740980899209B019C109E209F2FA046A121A238A30EA408A808A921AB08AD0FAE80B23FB360B480B51FB640B71FD4
:4003C000B908BE44C203C60EC804C9FFCAFFCBFFCF83D804D904DA04DB04DC99DD09DF01000801020309050A070909080A820B040D0A0F091141120813081402152A192051
:400400001D041F9024022508268027082A082B102C402D402E10351437813CA03D0540804240462057405A015B415EA6600263026540661668026A406D90835089408B01C6
:400440008E409142920693489422953D96B197809808994B9A109CF29D309F14A040A31CA480A54AA604A702A814AC08B780C0FFC2FFC4FFCAB0CCF0CEF0D040D6F8D8F8B5
:40048000E280E66CE802EC1000200250061008400A3010621208146216041A031C801D01207C2201261C2A102E10306031013280341F3E043F01580B59045B045C095F01FF
:4004C000802381C8820C85C8860189C88A108C2F8D228FCC9130922F9302942395C8960899029A409C239F48A020A1C8A202A423A604A780A880A9D4AC0FAD80AF01B01FA9
:40050000B161B260B380B480B51FB808B902BE10BF14C062C702C810C9FFCAFFCBFFCD20CEF0D110D804D904DA04DB04DC99DD09DF01E108E240E340E480E640E7400040C6
:40054000012102200505070409220A110C800E040F6010481144148016221710180419021B0C1C201D011E0420C0212122092501281029022B052E202F04311A32603504B8
:4005800036443842392840044A0850085F80660267806B026E807C02810188108B028D018F40900891409348951096209780980899489A109CA29D019E029F14A351A54004
:4005C000A604A702AB80AF40B548C07FC2FFC4FFCA0FCC1FCE0FD004D204D610DE80E008E220E680EA81012002010340070308100A080B100F10100413101408161217100D
:400600001B1C1C1B1D401F20217C2301250227042B102D022F083001316032023418351F36043E40580B590B5B045C995F01804082308610888089018A308B028D028F0133
:4006400090E2920894E296049A109E03A0FCA201A420A6D0AE1CAF01B2E0B301B502B61FB928BBC0BF14D608D80BD918DB04DC99DD90DF0100110501071408800A600C019B
:400680000D100E0A0F60121013041480162218101A801B501D401E121F0C23442409250828812A202B182F02310132403314380139203B843D905C205F80640867026A40A1
:4006C0006D40780279407B017C027F0189088E10C07AC2FDC4B0CA1FCC0FCE3FD630D830DE91E63080028E409240A402AD40B301E204E640EC01EE0A878092409D409F8071
:40070000A402A701A810B614E202EA40EC010040021105C20724082009200A110BC00D020F4810F11202139014F11604179C18101AE11B901E0E1F0320F1220823902480D4
:40074000261127902B902DFC2F0134F035E0360F371F3A803B20580B590B5B045C995F018001821A8411850186068C059C01A703AC05AD03B101B20CB302B410B501B603BE
:40078000BE10BF11C006C6ECC80CC9FFCAFFCBFFD004D601D804D904DA04DB04DC99DD09DF01E2C00080010803800410050109800C420E080F201140166019681B801C02B3
:4007C0001E081F6020042106220223842510264029482C422F6435013640371439083A403B403D203E083F4045104604492050105180680869056A106B236F027040713001
:40080000728173087C127F04842091529280934894A4952596119784980299039A289B149C309D109E069FE1A042A10CA288A314A40CA522A655A722A920B4C8B640B7042C
:40084000C05DC2F1C431CAFACCF0CE72D204DE80E202E608EE10000101120324040706080760088009800A100C0F0D700EF00F01104011421280131014801620187019069A
:400880001A801C011D0420082104220424012708280829042A022E0F2F10318032FF3370350F39203E043F01580459045B045C905F018210838487848A108B848D168E10FE
:4008C0008F41901C920193E49402960497849A1C9B189E03A002A101A208A306A510A610A722AA10AB84ADE7AF08B5F8B61FB707BB80D80BD90BDC99DF01008001050208F7
:400900000410058109080A810B040C020E0A0F2010081202130A166819081E02200821142211232425802610290A2A802B042C022E222F20329235013614395A3C123D0820
:400940003E083F40458046024801498058105B206A407B307C028240860190109157928193409520980299039AE09B0A9C109E02A04BA10CA28EA314A404A611AB40AF2038
:40098000B001C0DFC2FFC47FCAFFCCEDCEFFDE80E402EA80EE021B011F0232203302350836803B408908C630CCF0CE1030013180344037043A013F80822088028B1097407A
:4009C0009D08A108A620AE80AF01CCF0CE60E6805108560883408508932097409A089C419D089F04A002A108A580A740AA09D460E220E610EA20EE808108844185809320A9
:400A000097409C41A002A580AB04E2C0E650EA8014407002C404DC015840601084028C108D20A40AA808B040D401D802E008E402EA011B01811087019A109C40A120A4082A
:400A4000AE10C6080A040B200E200F01861487108E0297019A109D10A120A408AB01B040C20FE408E8012620932097409902A002A108B502C82052015310550870027502C3
:400A80008E0193209902A002A108AA20AF40D4E0DC80DE20EC80EE40051008080C080F041F105120561059206202840887049A109D10A120A202A408C001C20DC601D407CD
:400AC000D80285209D20AF1001010B010D010F0111011D0100FF01AB02021105BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B8F6
:400B00004700470000010000800000028200820000000000000707000700000027001801270018010004000000050000000000000000000000000000000000000000000002
:400000000145004008520040015B004001650040020101402C0201402D0301400F0501400B0701406308014056090140570A01404A0B0140500C01404A0D0140480E014042
:40004000390F01400415014004170140671801404B190140361A0140471B01400D40014010410140134201400D43014004440140064501400A460140084701400B48014097
:4000800010490140144C01400C4D014006500140045101407E02080109441180180219016009610A7C402721290AE204E6080F011001150126012E012F013001310138026A
:4000C00039023E013F01580459045B045F01810883048C018D0E910194029701A404AD04AE04AF0AB004B102B301B402B50CB601B802B908BE51BF04D608D804D909DB0401
:40010000DC90DD90DF01020208080C02100213081540170818081A801B801C80200821012210231026012A802C02300233043708388039023D405840600268026B0C6D4004
:40014000782088028B109840B040C001C214C4A5CA18CC43CE19D608D808DE04E208E68080088201928094809C289E80A201A602B044B380B404B610E604EA01EE44804068
:40018000842094809C209E80A602AA40E211E608EA01EE0C00800121020403020403052106FC0704080109210A020B080C040E100F4010FF11011404152F162019201A0495
:4001C0001B011C041D211EF91F0E220224042608271028042A402B2F2D0F2E02311F336034FF39083A0C3E104003450C470E481149FF4AFF4BFF50045609581859045A044E
:400200005B045C995D995F0162C081028407850186388702880189048C028D048E04902091049208940895029610983F99029C089D049E30A105A503A704A802A902AA0417
:40024000AC3FAD02B43FB507B63FBF10D608D804D904DB04DC09DD90DF010010010803010490050909240A420C030E020F24100A15081602172119401A021B501E101F107B
:400280002120242025052603272029982B402C022F18300831803202332035983701388039013B183C803D113E0447084F0856085A805F4062806302670269806B026C0881
:4002C0006D116E846F21759076467F01814087018E08908094209581971499049C019DC99E449F03A382A408A682A729C0F7C2FFC4F3CA7FCCFFCEFFD040D618D818DE1080
:40030000E210EA01EE0C0101041205010624084209800A100B200C040D700F80107011081201130414061508170219071A081B081D801E601F102140221023802404250F16
:4003400027F028042B0F2C802D01300F320F348035FF3670380A3B0C3E103F105804590A5B045C995F01824185F186418704888089208A018B118CF18E0291F1924E930218
:40038000954096419711980199109A149BE19F0EA1F1A308A580A641A711A801AA28B00FB10FB3F0B4F0BA02BB02D80BD90BDB04DC99DF010108020203080510076208017B
:4003C00009200A220D020E1410021240131414081508188819041A221B821C801D121F1021282501277029892B082D022F103008314033213608376238A039013A043E1454
:4004000068026D407A107E80800481408D108F019108921495809702980899029D109F72A218A440A509A701A809B340B408B502B640C0F7C27FC46FCACFCCFFCE6FDE8298
:40044000E401E8F0EC20EE40020103C4070808050A020B040C380D200F82101011C7120813181602170418081A301B041F302520274128042B042C062E012F043020320715
:40048000330F341837F43B083E04580459085B045C995F0182108303840285628604870489208A108B508DFC8E108F0192039420964097809A109B1C9C409E209F10A07C22
:4004C000A162A201A308A61CA710A940AA10AB30AC02AE08AF10B01FB11FB360B460B780D808D908DC99DF0100A001800204036004020706081009080A800B010C080E8878
:400500001218134215401608180419201A841B201C081EA02105220923112504268429922A242D802E102F20301031083350340136883722380439823B103D0A3E203F4070
:40054000611062106D406F047B037F028A08900292509508968297129C019D329F62A018A444A501A690A721C0DFC27FC4CFCA7FCCFECEFFDE18E020E208EA8000020120A4
:40058000024803C004C20502062407440A030B900E9011FC129013011690170318201AC01B901E901F9020FC21C222012328269027902A9C2B9C2F90321F331F34E037E015
:4005C0003A203B80420647E0482049FF4AFF4BFF4F83580859085A045B045C995D095F0180E28208861088E28A049210942096D09A03A0FCA201A440A630A880AA30AE1C91
:40060000B2E0B41FD808DB04DC09DF01009001C6032005260610072009010A140B420D200E100F40121013121548171218101A241B101C201D201E101F0820022204230148
:4006400029862B08300131103204335039043B1148054A0252015AAA6120628263206A406C097B037F0282018610C07FC27FC4FECA0FCC0FCE07D204D60FD80FDE188A02E6
:40068000A602E604EE02A602B680E401EE020010022003020406063807040802090C0A040BF10C080D0C0F20100813081408150C17101808190C1B401C081F0221802204B3
:4006C000230C250327FC281A29012A202B022DFF2E01303F31FF3E013F01580459045B045F0181238280830C842286CC870188308A028B2F8CC88D0F9123924894C8952367
:40070000970898D499239B049CC89F10A0C8A120A302A4C8A52FA802AB40AC80AE01B080B261B31FB41FB560B760B808B9A0BE11C046C620C808C9FFCAFFCBFFCD20CEF004
:40074000D110D804D904DA04DB04DC99DD09DF01E108E240E340E480E640E7400010010803820490050909080A420B040E020F941002110A120415401721184019181C80B4
:40078000218C2314274029182A022D4A2F0830083361350436413720381039413A043D013F9440404A085010584059205F806C026D4082409080910192029410950896C023
:4007C000971C99049A069B219D099F08A008A105A2C1A3E6A442A580A708B340B610C0FFC2FFC4DFCAF7CCFFCEFFD001D204D61CEE120008060209020D01100411041202D8
:4008000018041F04220428012C063006310132013302340835043A023E143F055608580459045B045C995D905F0181028405881090019236930198019C319E0AAC05AD012B
:40084000B00CB102B210B420B501B603BE04BF11D608D804D904DB04DC09DD90DF0101080240051809200A400E820F08100811401280154019231A501C101D101E821F0E2C
:4008800020402120220827882F02300232803601382039803F0158205A805F40600862406701680869806F0180408210844887408B018E4090209140941096409708982064
:4008C00099089C409D239F88A002A280A304A608A708B020B410C06CC2DAC48BCA10CC89CE8CD61CD81CE001E622EE021B011F083140330836843B4083408540C630CCF07B
:40090000CE10E220E61030803204364037043A083F80848088029740A340A604AE80AF01CCF0CE60E2105004570880049002960897409A049E089F04A002A120A644A80182
:40094000AA04B520D460E680EA80EE208E08900297409A049E08A002A120A640AA04AB0CAE04EA80EEB014407008C404DC0160808740A408B040D802EA011B0482209780E6
:400980009940B008B140B480C608E809EC040B880F4197899940A220AB05AF04C20F24088004900297409A04A002A120AE40C820E480EE4050015120560470027F018301EC
:4009C00090029A04A002A120AF40D4E0DC80DE20E620EE40070208080D010E021F10520854085940622095029940A220AF40B101C001C20DC601D407D802EA04800887021E
:400A00008E019B02A201A408AF10B008B608E208EA01EC02010109010D010F0111011D0100FF01AB02021105BF0000A09F001F0000000000000000001000000040000000B7
:400A400000000000C0000000FF0000B84700470000010000800000008000800000000000000707000700000027001801270018010004000000050000000000000000000052
:400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036
:400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6
:400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5
:400B40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075
:400B80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035
:400BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F5
@@ -4615,12 +4615,12 @@
:0200000490105A
:04000000BC90ACAF55
:0200000490303A
:0200000090D09E
:0200000018EFF7
:0200000490402A
:4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0
:400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
:400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040
:4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
:0200000490501A
:0C00000000012E16106900002E30A139FE
:0C00000000012E16106900002E30295857
:00000001FF

View File

File diff suppressed because it is too large Load Diff

View File

Binary file not shown.

View File

@@ -221,7 +221,7 @@ static int usb_read(int ep, uint8_t buffer[FRAME_SIZE])
static void cmd_get_version(struct any_frame* f)
{
DECLARE_REPLY_FRAME(struct version_frame, F_FRAME_GET_VERSION_REPLY);
r.version = FLUXENGINE_VERSION;
r.version = FLUXENGINE_PROTOCOL_VERSION;
send_reply((struct any_frame*) &r);
}

View File

@@ -37,11 +37,6 @@ FluxEngine features are available with the GreaseWeazle and it works out-of-the
box. See the [dedicated GreaseWeazle documentation page](doc/greaseweazle.md)
for more information.
**Important note.** On 2020-04-02 I changed the bytecode format (and firmware).
Flux files will need to be upgraded with `fluxengine upgradefluxfile`. The new
format should be more reliable and use way, way less bandwidth. Sorry for the
inconvenience.
Where?
------
@@ -66,14 +61,18 @@ following friendly articles:
flux files and image files ∾ knowing what you're doing
- [Using GreaseWeazle hardware with the FluxEngine client
software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to
go for help
software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to
go for help
- [Configuring for your drive](doc/drives.md) ∾ but I don't have a 80 track
drive! ∾ reading and writing 40 track disks ∾ Shugart and Apple II
- [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact
science ∾ the sector map ∾ clock detection and the histogram
science ∾ the sector map ∾ clock detection and the histogram
- [Checking your drive](doc/driveresponse.md) ∾ you can't do that with that ∾
measuring your drive's ability to work with exotic formats
- [Disk densities](doc/driveresponse.md) ∾ what's the difference between an HD
and DD disk? ∾ you can't do that with that ∾ measuring your drive's ability to
work with exotic formats ∾ I think my drive is broken
Which?
------
@@ -104,6 +103,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 |
| [Elektronika BK](doc/disk-bd.md) | 🦄 | 🦄 | Soviet Union PDP-11 clone |
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |
| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |

View File

@@ -68,7 +68,7 @@ public:
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location&) const override
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
return sectors;

View File

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

View File

@@ -11,6 +11,8 @@
#define APPLE2_SECTOR_LENGTH 256
#define APPLE2_ENCODED_SECTOR_LENGTH 342
#define APPLE2_SECTORS 16
extern std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config);
extern std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config);

View File

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

View File

@@ -144,6 +144,14 @@ public:
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}
std::set<unsigned> requiredSectors(const Location& location) const override
{
std::set<unsigned> sectors;
for (int sectorId = 0; sectorId < APPLE2_SECTORS; sectorId++)
sectors.insert(sectorId);
return sectors;
}
};
std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config)

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -139,7 +139,7 @@ public:
bw += decodeFmMfm(bits).slice(0, IBM_IDAM_LEN);
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, _sector->physicalCylinder, _sector->physicalHead);
getTrackFormat(trackdata, _sector->physicalTrack, _sector->physicalHead);
_sector->logicalTrack = br.read_8();
_sector->logicalSide = br.read_8();
@@ -155,7 +155,7 @@ public:
if (trackdata.ignore_side_byte())
_sector->logicalSide = _sector->physicalHead;
if (trackdata.ignore_track_byte())
_sector->logicalTrack = _sector->physicalCylinder;
_sector->logicalTrack = _sector->physicalTrack;
for (int sector : trackdata.ignore_sector())
if (_sector->logicalSector == sector)
@@ -203,10 +203,10 @@ public:
_sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location& location) const override
{
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, cylinder, head);
getTrackFormat(trackdata, location.logicalTrack, location.head);
std::set<unsigned> s;
if (trackdata.has_sectors())
@@ -227,12 +227,12 @@ public:
}
private:
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head) const
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, unsigned track, unsigned head) const
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_cylinder() && (f.cylinder() != cylinder))
if (f.has_track() && (f.track() != track))
continue;
if (f.has_head() && (f.head() != head))
continue;

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ message IbmDecoderProto {
optional int32 max_sector = 2 [(help) = "require these sectors to exist for a good read"];
}
optional int32 cylinder = 7 [(help) = "if set, the format applies only to this track"];
optional int32 track = 7 [(help) = "if set, the format applies only to this track"];
optional int32 head = 8 [(help) = "if set, the format applies only to this head"];
optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
@@ -42,13 +42,12 @@ message IbmEncoderProto {
optional int32 max_sector = 2 [(help) = "write these sectors (in order) on each track"];
}
optional int32 cylinder = 15 [(help) = "if set, the format applies only to this track"];
optional int32 track = 15 [(help) = "if set, the format applies only to this track"];
optional int32 head = 16 [(help) = "if set, the format applies only to this head"];
optional double track_length_ms = 1 [(help) = "length of track"];
optional int32 sector_size = 2 [default=512, (help) = "number of bytes per sector"];
optional bool emit_iam = 3 [default=true, (help) = "whether to emit an IAM record"];
optional double clock_rate_khz = 5 [(help) = "data clock rate"];
optional double target_clock_period_us = 5 [default=4, (help) = "data clock rate on target disk"];
optional bool use_fm = 6 [default=false, (help) = "whether to use FM encoding rather than MFM"];
optional int32 idam_byte = 7 [default=0x5554, (help) = "16-bit raw bit pattern of IDAM byte"];
optional int32 dam_byte = 8 [default=0x5545, (help) = "16-bit raw bit pattern of DAM byte"];
@@ -58,6 +57,7 @@ message IbmEncoderProto {
optional int32 gap3 = 12 [default=80, (help) = "size of gap 4 (the post-data or format gap)"];
optional bool swap_sides = 14 [default=false, (help) = "swap side bytes when writing"];
optional int32 gap_fill_byte = 18 [default=0x9254, (help) = "16-bit raw bit pattern of gap fill byte"];
optional double target_rotational_period_ms = 1 [default=200, (help) = "rotational period of target disk"];
oneof required_sectors {
SectorsProto sectors = 17 [(help) = "require these sectors to exist for a good read"];

View File

@@ -144,7 +144,7 @@ public:
auto header = toBytes(readRawBits(7*8)).slice(0, 7);
uint8_t encodedTrack = decode_data_gcr(header[0]);
if (encodedTrack != (_sector->physicalCylinder & 0x3f))
if (encodedTrack != (_sector->physicalTrack & 0x3f))
return;
uint8_t encodedSector = decode_data_gcr(header[1]);
@@ -155,7 +155,7 @@ public:
if (encodedSector > 11)
return;
_sector->logicalTrack = _sector->physicalCylinder;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = decode_side(encodedSide);
_sector->logicalSector = encodedSector;
uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
@@ -183,16 +183,18 @@ public:
_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12));
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location& location) const override
{
unsigned track = location.logicalTrack;
int count;
if (cylinder < 16)
if (track < 16)
count = 12;
else if (cylinder < 32)
else if (track < 32)
count = 11;
else if (cylinder < 48)
else if (track < 48)
count = 10;
else if (cylinder < 64)
else if (track < 64)
count = 9;
else
count = 8;

View File

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

View File

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

View File

@@ -133,7 +133,7 @@ public:
return;
if (_sector->logicalTrack > 76)
return;
if (_sector->logicalTrack != _sector->physicalCylinder)
if (_sector->logicalTrack != _sector->physicalTrack)
return;
br.read(10); /* OS data or padding */
@@ -144,19 +144,19 @@ public:
* Once the checksum type is determined, it will be used for the
* entire disk.
*/
if (_checksumType == 0) {
if (_checksumType == MicropolisDecoderProto::AUTO) {
/* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS checksums */
if (wantChecksum == micropolisChecksum(bytes.slice(1, 2+266))) {
_checksumType = 1;
_checksumType = MicropolisDecoderProto::MICROPOLIS;
} else if (wantChecksum == mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE))) {
_checksumType = 2;
_checksumType = MicropolisDecoderProto::MZOS;
std::cout << "Note: MZOS checksum detected." << std::endl;
}
}
uint8_t gotChecksum;
if (_checksumType == 2) {
if (_checksumType == MicropolisDecoderProto::MZOS) {
gotChecksum = mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE));
} else {
gotChecksum = micropolisChecksum(bytes.slice(1, 2+266));
@@ -173,7 +173,7 @@ public:
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location& location) const override
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
return sectors;
@@ -181,7 +181,7 @@ public:
private:
const MicropolisDecoderProto& _config;
int _checksumType; /* -1 = auto, 1 = Micropolis, 2=MZOS */
MicropolisDecoderProto_ChecksumType _checksumType; /* -1 = auto, 1 = Micropolis, 2=MZOS */
};
std::unique_ptr<AbstractDecoder> createMicropolisDecoder(const DecoderProto& config)

View File

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

View File

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

View File

@@ -65,7 +65,7 @@ public:
gotChecksum += br.read_be16();
uint16_t wantChecksum = br.read_be16();
_sector->logicalTrack = _sector->physicalCylinder;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalHead;
_sector->logicalSector = _currentSector;
_sector->data = bytes.slice(0, SECTOR_SIZE).swab();

View File

@@ -152,7 +152,7 @@ public:
_sector->logicalSide = _sector->physicalHead;
_sector->logicalSector = _hardSectorId;
_sector->logicalTrack = _sector->physicalCylinder;
_sector->logicalTrack = _sector->physicalTrack;
if (headerSize == NORTHSTAR_HEADER_SIZE_DD) {
br.read_8(); /* MFM second Sync char, usually 0xFB */
@@ -164,7 +164,7 @@ public:
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location&) const override
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return sectors;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

37
doc/disk-bk.md Normal file
View File

@@ -0,0 +1,37 @@
Disk: Elektronica BK
====================
The BK (an abbreviation for бытовой компьютер --- 'home computer' in Russian)
is a Soviet era personal computer from Elektronika based on a PDP-11
single-chip processor. It was the _only_ official, government approved home
computer in mass production at the time.
It got a floppy interface in 1989 when the 128kB BK-0011 was released. This
used a relatively normal double-sided IBM scheme format with 80 sectors and ten
sectors per track, resulting in 800kB disks. The format is, in fact, identical
to the Atari ST 800kB format. Either 5.25" or 3.5" drives were used depending
on what was available at the time, with the same format on both.
Reading disks
-------------
Just do:
```
fluxengine read bk800 -o bk800.img
```
You should end up with an `bk800.img` containing all the sectors concatenated
one after the other in CHS order. This will work on both 5.25" and 3.5" drives.
Writing disks
-------------
Just do:
```
fluxengine write bk800 -i bk800.img
```
This will write the disk image to either a 5.25" or 3.5" drive.

View File

@@ -58,7 +58,8 @@ as the disk ID in the BAM has to be copied to every sector on the disk.
Reading 1581 disks
------------------
1581 disks are just another version of the standard B
1581 disks are just another version of the standard IBM scheme.
Just do:
```

View File

@@ -1,25 +1,79 @@
Analysing drive response
========================
Disk and drive density
======================
**tl;dr:** with 5.25" drives, it is _vitally important_ that you set
`--drive.high_density=0|1` correctly when working with disks.
Not all PC drives are made equal. Some are less equal than others.
The way floppy disk storage works is that the floppy drive controller will
generate a series of pulses, which the drive stores on the disk. Then, when the
disk is read, the drive will reproduce the same series of pulses and return it
to the floppy drive controller. The data is stored in the intervals between
to the floppy drive controller. The data is stored by the intervals between
pulses.
The problem is that some PC drives assume that they're going to be used with
IBM scheme disks, which use particular pulse intervals --- in the case of DD
disks, intervals are always 4us, 6us or 8us. So, in a misguided attempt to
improve reliability, they sometimes... tidy... the incoming pulse stream. This
can have nasty effects if you're not a disk which _doesn't_ use those intervals.
However, the underlying physics of the magnetic media put limitations on how far
apart the pulses can be. If they're too close, they will physically move further
apart... and if they're too far apart, the drive will detect spurious pulses
that don't exist in real life (due to the way the drive's automatic gain
adjustment on the head amplifiers work).
In addition, they won't work properly if the intervals are too great, or too
small. Partly this is a limitation of the underlying physics of the magnetic
media, and partly it's due to the drive's automatic gain adjustment: if the
drive doesn't see a pulse, it'll start ramping up the gain of its amplifier,
until it starts interpreting random noise as a valid pulse.
So, it's very important what kind of disks you use, and what kind of drives you
put those disks in: these can vary.
Disk densities
--------------
Disks usually come in two varieties: SD/DD/QD disks, and HD disks. (There's
also ED and TD but they're fairly specialist and FluxEngine doesn't support
them.)
SD (single density), DD (double density) and QD (quad density) actually refer to
the way the data is stored on the disk. SD usually implies FM, and DD implies
MFM, which allows 60% more data; QD implies MFM _plus_ narrower tracks, doubling
the track count and hence the amount of data. This distinction was important
back when you had to buy pre-formatted disks, so you needed disks which matched
your drive. FluxEngine, of course, formats them itself.
HD disks use a different magnetic medium, based on cobalt rather than iron
oxide. This allows the pulses on the disk to be much closer together. This
doubles the amount of data yet again. The downside of HD disks is that the
pulses _have_ to be closer together so you're likely to have problems if you try
to write SD/DD/QD formats onto HD disks.
It's also worth noting that 3.5" SD/DD/QD disks are much more like 3.5" HD disks
than 5.25" SD/DD/QD disks are like 5.25" HD disks. If you own an Amiga which
uses 3.5" DD disks, you'll know that HD disks will mostly work just fine in an
Amiga; but if you own a C64 with 5.25" SD disks, you'll know that HD disks are
likely to be error-prone.
Drive types
-----------
Of course, the disk itself is only half the story...
Old floppy disk drives vary in what formats they support. The usual variables are:
disk rotation speed (either 300rpm or 360rpm); number of tracks (either 40 or
80); and whether they can support HD disks. (Plus, of course, whether it's 3.5"
or 5.25", but the technology is largely identical so I'm ignoring that.)
'Modern' drives are pretty consistent in that they run at 300rpm (for a 3.5"
drive) or 360rpm (for a 5.25" drive), have 80 tracks, and support HD.
There are two additional wrinkles:
- the drive must be configured for SS/DD/QD media or HD media. 3.5" drives can
do this automatically, as there's a hole in the disk case which a drive sensor
can detect, but 5.25" drives don't, and must be told what medium is being used
by the computer. (FluxEngine uses the `--drive.high_density=0|1` option for
this.)
- some drives are designed to only support IBM formats, which use pulse widths
of 4µs, 6µs or 8µs, and when given pulses that aren't this wide they behave...
poorly.
Actually measuring things
-------------------------
FluxEngine has a tool to analyse a drive and report on this behaviour. It works
by writing a sequence of timed pulses to the disk, then reading them back and
@@ -27,52 +81,166 @@ seeing what the drive actually reports. To use it, do:
```
fluxengine analyse driveresponse --cylinder 0 \
--min-interval-us=0 --max-interval-us=30 --interval-step-us=.1 \
--min-interval-us=0 --max-interval-us=15 --interval-step-us=.1 \
--write-img=driveresponse.png
```
This will scan all intervals from 0us to 30us, at 0.1us steps, draw a graph,
and write out the result. The graphs look like this.
This will scan all intervals from 0µs to 15µs, at 0.1µs steps, draw a graph,
and write out the result.
Using mismatched media and drive configuration
----------------------------------------------
There are four possible options when using 5.25" media in my Panasonic JU-475
5.25" drive. The following table shows what the analysis tool shows for all
these options.
(Click to expand)
<div style="text-align: center">
<a href="sony-mpf920-dd.png"><img src="sony-mpf920-dd.png" style="width:40%" alt="Sony MPF-920, DD"></a>
<a href="sony-mpf920-hd.png"><img src="sony-mpf920-hd.png" style="width:40%" alt="Sony MPF-920, HD"></a>
<table class="datatable">
<tr>
<th></th>
<th>DD drive</th>
<th>HD drive</th>
</tr>
<tr>
<th>DD media</th>
<td>
<a href="ju475-dd-lo.png">
<img src="ju475-dd-lo.png" style="width:100%"
alt="Panasonic JU-475, DD media, DD drive setting">
</a>
</td>
<td>
<a href="ju475-dd-hi.png">
<img src="ju475-dd-hi.png" style="width:100%"
alt="Panasonic JU-475, DD media, HD drive setting">
</a>
</td>
</tr>
<tr>
<th>HD media</th>
<td>
<a href="ju475-hd-lo.png">
<img src="ju475-hd-lo.png" style="width:100%"
alt="Panasonic JU-475, HD media, DD drive setting">
</a>
</td>
<td>
<a href="ju475-hd-hi.png">
<img src="ju475-hd-hi.png" style="width:100%"
alt="Panasonic JU-475, HD media, HD drive setting">
</a>
</td>
</tr>
</table>
</div>
This is the analysis from the [Sony
MPF-920](https://docs.sony.com/release/MPF920Z.pdf) 3.5" drive I mostly use for
testing. The left-hand image shows the result from a DD disk, while the right
hand image shows the result from a HD disk.
The X axis shows the width of the pulse being sent to the drive. This gets
recorded to the disk (repeatedly). The Y axis shows a heatmap of the pulses read
back again. What we want to see is a solid diagonal line, showing that the
pulses are being read back accurately. However, once the pulse width gets wide
enough, spurious pulses appear, which show up on the graph as the rainbow smear
in the bottom left corner. You can see that this is much more pronounced on the
HD media than it is on the DD media --- the DD media is capable of accurately
reproducing pulses up to about 12µs, while the HD media only goes up to 7µs.
The horizontal axis is the width of pulse being written; the vertical axis and
heatmap shows the distribution of pulses being read back. You can see the
diagonal line, which represents correct pulses. The triangular smear in the
bottom right shows spurious pulses which are being read back because the
interval is too great; these start at about 12us for DD disks and 7us for HD
disks. This is an artifact of the different magnetic media for the two types of
disk.
This demonstrates that some formats, such as the [Apple II](doc/disk-apple2.md)
which uses a maximum pulse width of 8µs, cannot be written to HD media at all.
(This, by the way, is why you shouldn't use DD formats on HD disks. The
intervals on a DD disk can go up to 8us, which is on the edge of the ability of
an HD disk and drive to correctly report back the pulses.)
We would also normally see spurious pulses on the left of the graph, indicating
that pulses are too close together to be reproduced accurately, but we're not
--- the Panasonic is a good drive.
You also note the hard cut-off on the left: this represents the filter on the
drive, which will simply refuse to report pulse intervals shorter than about
1.5us. FluxEngine itself can't write intervals shorter than 2us.
For comparison, following is the same table for my Sony MPF-90 3.5" drive.
For comparison purposes, here's another set of graphs.
(Click to expand)
<div style="text-align: center">
<a href="fdd-90206-dd.png"><img src="fdd-90206-dd.png" style="width:40%" alt="FDD-90206, DD"></a>
<a href="fdd-90206-hd.png"><img src="fdd-90206-hd.png" style="width:40%" alt="FDD-90206, HD"></a>
<table class="datatable">
<tr>
<th></th>
<th>DD drive</th>
<th>HD drive</th>
</tr>
<tr>
<th>DD media</th>
<td>
<a href="mpf90-dd-lo.png">
<img src="mpf90-dd-lo.png" style="width:100%"
alt="Sony MPF-90, DD media, DD drive setting">
</a>
</td>
<td>
</td>
</tr>
<tr>
<th>HD media</th>
<td>
<a href="mpf90-hd-lo.png">
<img src="mpf90-hd-lo.png" style="width:100%"
alt="Sony MPF-90, HD media, DD drive setting">
</a>
</td>
<td>
<a href="mpf90-hd-hi.png">
<img src="mpf90-hd-hi.png" style="width:100%"
alt="Sony MPF-90, HD media, HD drive setting">
</a>
</td>
</tr>
</table>
</div>
As 3.5" drives autodetect the media type, I had to tape over the hole in a HD
disk to make the drive treat it as a DD disk. Unfortunately, doing it the other
way around would require drilling a hole in a DD disk, which I'm unwilling to
do! So there are only three graphs here.
We get the same smear of spurious pulses in the bottom left, but we _also_ get
some incorrect pulses on the left. In addition, the line itself only starts at
about 2.5µs --- it turns out that this drive simply returns nothing at all for
pulses shorter than that.
(FluxEngine itself can't write intervals shorter than 2us.)
And, finally, here is one additional pair of graphs.
<div style="text-align: center">
<table class="datatable">
<tr>
<th></th>
<th>DD drive</th>
<th>HD drive</th>
</tr>
<tr>
<th>DD media</th>
<td>
<a href="fdd-90206-dd.png">
<img src="fdd-90206-dd.png" style="width:100%" alt="FDD-90206, DD">
</a>
</td>
<td>
</td>
</tr>
<tr>
<th>HD media</th>
<td></td>
</td>
<td>
<a href="fdd-90206-hd.png">
<img src="fdd-90206-hd.png" style="width:100%" alt="FDD-90206, HD">
</a>
</td>
</tr>
</table>
</div>
This is from another drive I have; it's an unbranded combo
card-reader-and-floppy drive unit; the 90206 is the only identification mark it
has. The DD graph shows that intervals below about 4us are reported as double
card-reader-and-3.5"-floppy drive unit; the 90206 is the only identification mark it
has. The DD graph shows that intervals below about 4µs are reported as double
what they should be: so, this drive won't work on [Macintosh 800kB
formats](disk-macintosh.md) at all, because they use intervals starting at
2.6us, below this limit. But it should work on PC formats --- just.
2.6µs, below this limit. But it should work on PC formats --- just.

90
doc/drives.md Normal file
View File

@@ -0,0 +1,90 @@
Configuring for your drive
==========================
By default, the FluxEngine client assumes you have a PC 80 track double sided
high density drive, either 3.5" or 5.25", as these are the most common. This
may not be the case, and you need to tell the FluxEngine client what kind of
drive you have.
Forty track formats on an eighty track drive
--------------------------------------------
Forty-track drives have the same geometry as eighty-track drives, but have a
head that is twice as big, so halving the number of tracks on the disk.
Examples of forty track drives include the Commodore 1541, IBM 360kB, or the
Brother 120kB format (which uses a rare 3.5" single-sided forty-track drive).
When a forty-track disk is inserted into an eighty-track drive, then each head
position will only see _half_ of each track. For reading this isn't a problem
--- FluxEngine will actually read both halves and combine the results --- but
writing is more problematic. Traditionally, if you wanted to write a
forty-track disk in an eighty-track drive, you had to use a brand new disk; the
drive would write to one half of the track, leaving the other half blank. If
both halves contained data, then the wider head on a forty track drive would
pick both up, producing a garbled result. This led to a very confusing
situation where forty-track disks written on an eighty-track drive would read
and write fine on an eight-track drive but wouldn't work at all on a
forty-track drive.
FluxEngine is capable of both reading and writing forty-track formats on an
eighty-track drive. It avoids the situation described above by writing one half
of the track and then magnetically erasing the other half. This does produce a
weaker signal on the disk, but in my testing the disks work just fine in
forty-track drives.
Forty track formats on a forty track drive
------------------------------------------
If you actually have a forty track drive, you need to tell FluxEngine. This is
done by adding the special profile `40track_drive`:
```
fluxengine write ibm360 40track_drive -i image.img -d drive:0
```
It should then Just Work. This is supported by both FluxEngine and GreaseWeazle
hardware.
Obviously you can't write an eighty-track format using a forty-track drive!
Apple II drives
---------------
The Apple II had special drives which supported microstepping: when commanded
to move the head, then instead of moving in single-track steps as is done in
most other drives, the Apple II drive would move in quarter-track steps. This
allowed much less precise head alignment, as small errors could be corrected in
software. (The Brother word processor drives were similar.) The bus interface
is different from normal PC drives.
The FluxEngine client supports these with the `apple2_drive` profile:
```
fluxengine write apple2 apple2_drive -i image.img -d drive:0
```
This is supported only by GreaseWeazle hardware.
Shugart drives
--------------
PC drives have a standard interface which doesn't really have a name but is
commonly referred to as 'the PC 34-pin interface'. There are a few other
interfaces, most notably the Shugart standard. This is also 34 pin and is very
similar to the PC interface but isn't quite electrically compatible. It
supports up to four drives on a bus, unlike the PC interface's two drives, but
the drives must be jumpered to configure them. This was mostly used by older
3.5" drives, such as those on the Atari ST. [the How It Works
page](technical.md) for the pinout.
The FluxEngine client supports these with the `shugart_drive` profile:
```
fluxengine write atarist720 shugart_drive -i image.img -d drive:0
```
(If you have a 40-track Shugart drive, use _both_ `shugart_drive` and
`40track_drive`.)
This is supported only by GreaseWeazle hardware.

BIN
doc/ju475-dd-hi.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
doc/ju475-dd-lo.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
doc/mpf90-dd-lo.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
doc/mpf90-hd-hi.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
doc/mpf90-hd-lo.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -88,6 +88,54 @@ GreaseWeazle hardware interchangeably with FluxEngine hardware. See the
[dedicated page](greaseweazle.md) for more information.
## Drive interface pinouts
For reference, here are the FDC pinouts:
```ditaa
:-E -s 0.75
+--+--+ +--+--+
DISKCHG ---+34+33+ DISKCHG ---+34+33+
+--+--+ +--+--+
SIDE1 ---+32+31+ SIDE1 ---+32+31+
+--+--+ +--+--+
RDATA ---+30+29+ RDATA ---+30+29+
+--+--+ +--+--+
WPT ---+28+27+ WPT ---+28+27+
+--+--+ +--+--+
TRK00 ---+26+25+ TRK00 ---+26+25+
+--+--+ +--+--+
WGATE ---+24+23+ WGATE ---+24+23+
+--+--+ +--+--+
WDATA ---+22+21+ WDATA ---+22+21+
+--+--+ +--+--+
STEP ---+20+19+ STEP ---+20+19+
+--+--+ +--+--+
DIR/SIDE1 ---+18+17+ DIR/SIDE1 ---+18+17+
+--+--+ +--+--+
MOTEB ---+16+15+ MOTEB ---+16+15+
+--+--+ +--+--+
DRVSA ---+14+13+ DS3 ---+14+13+
+--+--+ +--+--+
DRVSB ---+12+11+ DS2 ---+12+11+
+--+--+ +--+--+
MOTEA ---+10+9 + DS1 ---+10+9 +
+--+--+ +--+--+
INDEX ---+8 +7 + INDEX ---+8 +7 +
+--+--+ +--+--+
n/c ---+6 +5 + DS4 ---+6 +5 +
+--+--+ +--+--+
n/c ---+4 +3 + INU ---+4 +3 +
+--+--+ +--+--+
REDWC ---+2 +1 + REDWC ---+2 +1 +
+--+--+ +--+--+
PC interface Shugart interface
Odd numbered pins are always grounded
```
### Some useful links
- [The floppy disk user's

View File

@@ -66,12 +66,12 @@ Configurations can be specified either on the command line or in text files.
Here are some sample invocations:
```
# Read an PC disk, producing a disk image with the default name (ibm.img),
# autodetecting all parameters
$ fluxengine read ibm
# Read an PC 1440kB disk, producing a disk image with the default name
# (ibm.img)
$ fluxengine read ibm1440
# Write a PC 1440kB disk to drive 1
$ fluxengine write ibm -i image.img -d drive:1
$ fluxengine write ibm1440 -i image.img -d drive:1
# Read a Eco1 CP/M disk, making a copy of the flux into a file
$ fluxengine read eco1 --copy-flux-to copy.flux -o eco1.ldbs
@@ -108,8 +108,9 @@ protobuf syntax](https://developers.google.com/protocol-buffers), which is
hierarchical, type-safe, and easy to read.
The `ibm1440` string above is actually a reference to an internal configuration
file containing all the settings for writing PC 1440kB disks. You can see all
these settings by doing:
file containing all the settings for writing PC 1440kB disks. You may specify
as many profile names or textpb files as you wish; they are all merged left to
right. You can see all these settings by doing:
```
$ fluxengine write ibm1440 --config
@@ -404,7 +405,7 @@ containing valuable historical data, and you want to read them.
Typically I do this:
```
$ fluxengine read brother -s drive:0 -o brother.img --copy-flux-to=brother.flux --decoder.write_csv_to=brother.csv
$ fluxengine read brother240 -s drive:0 -o brother.img --copy-flux-to=brother.flux --decoder.write_csv_to=brother.csv
```
This will read the disk in drive 0 and write out an information CSV file. It'll

View File

@@ -11,7 +11,7 @@ import "lib/drive.proto";
import "lib/mapper.proto";
import "lib/common.proto";
// NEXT_TAG: 16
// NEXT_TAG: 17
message ConfigProto {
optional string comment = 8;
optional bool is_extension = 13;
@@ -27,8 +27,9 @@ message ConfigProto {
optional DecoderProto decoder = 4;
optional UsbProto usb = 5;
optional RangeProto cylinders = 6;
optional RangeProto tracks = 6;
optional RangeProto heads = 7;
optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ];
optional SectorMappingProto sector_mapping = 14;
}

View File

@@ -60,12 +60,11 @@ std::unique_ptr<AbstractDecoder> AbstractDecoder::create(const DecoderProto& con
}
std::shared_ptr<const TrackDataFlux> AbstractDecoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap, unsigned physicalCylinder, unsigned physicalHead)
std::shared_ptr<const Fluxmap> fluxmap, const Location& location)
{
_trackdata = std::make_shared<TrackDataFlux>();
_trackdata->fluxmap = fluxmap;
_trackdata->physicalCylinder = physicalCylinder;
_trackdata->physicalHead = physicalHead;
_trackdata->location = location;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
@@ -73,8 +72,8 @@ std::shared_ptr<const TrackDataFlux> AbstractDecoder::decodeToSectors(
auto newSector = [&] {
_sector = std::make_shared<Sector>();
_sector->status = Sector::MISSING;
_sector->physicalCylinder = physicalCylinder;
_sector->physicalHead = physicalHead;
_sector->physicalTrack = location.physicalTrack;
_sector->physicalHead = location.head;
};
newSector();
@@ -218,7 +217,7 @@ uint64_t AbstractDecoder::readRaw64()
}
std::set<unsigned> AbstractDecoder::requiredSectors(unsigned cylinder, unsigned head) const
std::set<unsigned> AbstractDecoder::requiredSectors(const Location& location) const
{
static std::set<unsigned> set;
return set;

View File

@@ -18,23 +18,27 @@ extern void setDecoderManualClockRate(double clockrate_us);
extern Bytes decodeFmMfm(std::vector<bool>::const_iterator start,
std::vector<bool>::const_iterator end);
extern void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input, bool& lastBit);
extern void encodeFm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input);
extern void encodeMfm(std::vector<bool>& bits,
unsigned& cursor,
const Bytes& input,
bool& lastBit);
extern void encodeFm(
std::vector<bool>& bits, unsigned& cursor, const Bytes& input);
extern Bytes encodeMfm(const Bytes& input, bool& lastBit);
static inline Bytes decodeFmMfm(const std::vector<bool> bits)
{ return decodeFmMfm(bits.begin(), bits.end()); }
{
return decodeFmMfm(bits.begin(), bits.end());
}
class AbstractDecoder
{
public:
AbstractDecoder(const DecoderProto& config):
_config(config)
{}
AbstractDecoder(const DecoderProto& config): _config(config) {}
virtual ~AbstractDecoder() {}
static std::unique_ptr<AbstractDecoder> create(const DecoderProto& config);
static std::unique_ptr<AbstractDecoder> create(const DecoderProto& config);
public:
enum RecordType
@@ -45,47 +49,59 @@ public:
};
public:
std::shared_ptr<const TrackDataFlux> decodeToSectors(std::shared_ptr<const Fluxmap> fluxmap, unsigned cylinder, unsigned head);
void pushRecord(const Fluxmap::Position& start, const Fluxmap::Position& end);
std::shared_ptr<const TrackDataFlux> decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
const Location& location);
void resetFluxDecoder();
void pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end);
void resetFluxDecoder();
std::vector<bool> readRawBits(unsigned count);
uint8_t readRaw8();
uint16_t readRaw16();
uint32_t readRaw20();
uint32_t readRaw24();
uint32_t readRaw32();
uint64_t readRaw48();
uint64_t readRaw64();
uint8_t readRaw8();
uint16_t readRaw16();
uint32_t readRaw20();
uint32_t readRaw24();
uint32_t readRaw32();
uint64_t readRaw48();
uint64_t readRaw64();
Fluxmap::Position tell()
{ return _fmr->tell(); }
{
return _fmr->tell();
}
void seek(const Fluxmap::Position& pos)
{ return _fmr->seek(pos); }
{
return _fmr->seek(pos);
}
nanoseconds_t seekToPattern(const FluxMatcher& pattern);
void seekToIndexMark();
nanoseconds_t seekToPattern(const FluxMatcher& pattern);
void seekToIndexMark();
bool eof() const
{ return _fmr->eof(); }
{
return _fmr->eof();
}
nanoseconds_t getFluxmapDuration() const
{ return _fmr->getDuration(); }
nanoseconds_t getFluxmapDuration() const
{
return _fmr->getDuration();
}
virtual std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const;
virtual std::set<unsigned> requiredSectors(const Location& location) const;
protected:
virtual void beginTrack() {};
virtual void beginTrack(){};
virtual nanoseconds_t advanceToNextRecord() = 0;
virtual void decodeSectorRecord() = 0;
virtual void decodeDataRecord() {};
virtual void decodeDataRecord(){};
const DecoderProto& _config;
std::shared_ptr<TrackDataFlux> _trackdata;
const DecoderProto& _config;
std::shared_ptr<TrackDataFlux> _trackdata;
std::shared_ptr<Sector> _sector;
std::unique_ptr<FluxDecoder> _decoder;
std::vector<bool> _recordBits;
std::unique_ptr<FluxDecoder> _decoder;
std::vector<bool> _recordBits;
private:
FluxmapReader* _fmr = nullptr;

View File

@@ -13,6 +13,10 @@ public:
std::vector<bool> readBits(unsigned count);
std::vector<bool> readBits(const Fluxmap::Position& until);
std::vector<bool> readBits()
{
return readBits(UINT_MAX);
}
private:
nanoseconds_t nextFlux();

View File

@@ -2,13 +2,35 @@ syntax = "proto2";
import "lib/common.proto";
message DriveProto {
optional int32 drive = 1 [default = 0, (help) = "which drive to write to (0 or 1)"];
optional IndexMode index_mode = 2 [default = INDEXMODE_DRIVE, (help) = "index pulse source"];
optional int32 hard_sector_count = 3 [default = 0, (help) = "number of hard sectors on disk"];
optional bool high_density = 4 [default = true, (help) = "set if this is a high density disk"];
optional bool sync_with_index = 5 [default = false, (help) = "start reading at index mark"];
optional double revolutions = 6 [default = 1.2, (help) = "number of revolutions to read"];
// Next: 14
message DriveProto
{
optional int32 drive = 1
[ default = 0, (help) = "which drive to write to (0 or 1)" ];
optional IndexMode index_mode = 2
[ default = INDEXMODE_DRIVE, (help) = "index pulse source" ];
optional int32 hard_sector_count = 3
[ default = 0, (help) = "number of hard sectors on disk" ];
optional bool high_density = 4
[ default = true, (help) = "set if this is a high density disk" ];
optional bool sync_with_index = 5
[ default = false, (help) = "start reading at index mark" ];
optional double revolutions = 6
[ default = 1.2, (help) = "number of revolutions to read" ];
optional int32 tracks = 7
[ default = 81, (help) = "Number of tracks supported by drive" ];
optional int32 heads = 8
[ default = 2, (help) = "Number of heads supported by drive" ];
optional int32 head_bias = 9 [
default = 0,
(help) = "Bias to apply to the head position (in tracks)"
];
optional int32 head_width = 10
[ default = 1, (help) = "Width of the head (in tracks)" ];
optional int32 tpi = 11 [ default = 96, (help) = "TPI of drive" ];
optional double rotational_period_ms = 12
[ default = 0, (help) = "Rotational period of the drive in milliseconds (0 to autodetect)"];
}
// vim: ts=4 sw=4 et

View File

@@ -15,45 +15,44 @@
#include "lib/encoders/encoders.pb.h"
#include "protocol.h"
std::unique_ptr<AbstractEncoder> AbstractEncoder::create(const EncoderProto& config)
std::unique_ptr<AbstractEncoder> AbstractEncoder::create(
const EncoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>> encoders =
{
{ EncoderProto::kAmiga, createAmigaEncoder },
{ EncoderProto::kApple2, createApple2Encoder },
{ EncoderProto::kBrother, createBrotherEncoder },
{ EncoderProto::kC64, createCommodore64Encoder },
{ EncoderProto::kIbm, createIbmEncoder },
{ EncoderProto::kMacintosh, createMacintoshEncoder },
{ EncoderProto::kMicropolis,createMicropolisEncoder },
{ EncoderProto::kNorthstar, createNorthstarEncoder },
{ EncoderProto::kTids990, createTids990Encoder },
{ EncoderProto::kVictor9K, createVictor9kEncoder },
};
static const std::map<int,
std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>>
encoders = {
{EncoderProto::kAmiga, createAmigaEncoder },
{EncoderProto::kApple2, createApple2Encoder },
{EncoderProto::kBrother, createBrotherEncoder },
{EncoderProto::kC64, createCommodore64Encoder},
{EncoderProto::kIbm, createIbmEncoder },
{EncoderProto::kMacintosh, createMacintoshEncoder },
{EncoderProto::kMicropolis, createMicropolisEncoder },
{EncoderProto::kNorthstar, createNorthstarEncoder },
{EncoderProto::kTids990, createTids990Encoder },
{EncoderProto::kVictor9K, createVictor9kEncoder },
};
auto encoder = encoders.find(config.format_case());
if (encoder == encoders.end())
Error() << "no encoder specified";
auto encoder = encoders.find(config.format_case());
if (encoder == encoders.end())
Error() << "no encoder specified";
return (encoder->second)(config);
return (encoder->second)(config);
}
Fluxmap& Fluxmap::appendBits(const std::vector<bool>& bits, nanoseconds_t clock)
{
nanoseconds_t now = duration();
for (unsigned i=0; i<bits.size(); i++)
{
now += clock;
if (bits[i])
{
unsigned delta = (now - duration()) / NS_PER_TICK;
nanoseconds_t now = duration();
for (unsigned i = 0; i < bits.size(); i++)
{
now += clock;
if (bits[i])
{
unsigned delta = (now - duration()) / NS_PER_TICK;
appendInterval(delta);
appendPulse();
}
}
appendPulse();
}
}
return *this;
return *this;
}

View File

@@ -1,9 +1,10 @@
#ifndef ENCODERS_H
#define ENCODERS_H
class Fluxmap;
class EncoderProto;
class Fluxmap;
class Image;
class Location;
class Sector;
class AbstractEncoder
@@ -16,10 +17,9 @@ public:
public:
virtual std::vector<std::shared_ptr<const Sector>> collectSectors(
int physicalCylinder, int physicalHead, const Image& image) = 0;
const Location& location, const Image& image) = 0;
virtual std::unique_ptr<Fluxmap> encode(int physicalCylinder,
int physicalHead,
virtual std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) = 0;
};

View File

@@ -10,7 +10,7 @@ enum FluxFileVersion {
}
message TrackFluxProto {
optional int32 cylinder = 1;
optional int32 track = 1;
optional int32 head = 2;
repeated bytes flux = 3;
}
@@ -19,5 +19,6 @@ message FluxFileProto {
optional int32 magic = 1;
optional FluxFileVersion version = 2;
repeated TrackFluxProto track = 3;
optional double rotational_period_ms = 4;
}

View File

@@ -239,6 +239,7 @@ void BoolFlag::set(const std::string& value)
else
Error() << "can't parse '" << value << "'; try 'true' or 'false'";
_callback(_value);
_isSet = true;
}
const std::string HexIntFlag::defaultValueAsString() const

View File

@@ -110,6 +110,9 @@ public:
operator const T& () const
{ return get(); }
bool isSet() const
{ return _isSet; }
void setDefaultValue(T value)
{
_value = _defaultValue = value;
@@ -120,6 +123,7 @@ public:
protected:
T _defaultValue;
T _value;
bool _isSet = false;
std::function<void(const T&)> _callback;
};
@@ -133,7 +137,7 @@ public:
{}
const std::string defaultValueAsString() const { return _defaultValue; }
void set(const std::string& value) { _value = value; _callback(_value); }
void set(const std::string& value) { _value = value; _callback(_value); _isSet = true; }
};
class IntFlag : public ValueFlag<int>
@@ -146,7 +150,7 @@ public:
{}
const std::string defaultValueAsString() const { return std::to_string(_defaultValue); }
void set(const std::string& value) { _value = std::stoi(value); _callback(_value); }
void set(const std::string& value) { _value = std::stoi(value); _callback(_value); _isSet = true; }
};
class HexIntFlag : public IntFlag
@@ -171,7 +175,7 @@ public:
{}
const std::string defaultValueAsString() const { return std::to_string(_defaultValue); }
void set(const std::string& value) { _value = std::stod(value); _callback(_value); }
void set(const std::string& value) { _value = std::stod(value); _callback(_value); _isSet = true; }
};
class BoolFlag : public ValueFlag<bool>

View File

@@ -1,6 +1,9 @@
#ifndef FLUX_H
#define FLUX_H
#include "bytes.h"
class Fluxmap;
class Sector;
class Image;
@@ -12,10 +15,33 @@ struct Record
Bytes rawData;
};
struct Location
{
unsigned physicalTrack;
unsigned logicalTrack;
unsigned head;
unsigned groupSize;
bool operator==(const Location& other) const
{
if (physicalTrack == other.physicalTrack)
return true;
return head == other.head;
}
bool operator<(const Location& other) const
{
if (physicalTrack < other.physicalTrack)
return true;
if (physicalTrack == other.physicalTrack)
return head < other.head;
return false;
}
};
struct TrackDataFlux
{
unsigned physicalCylinder;
unsigned physicalHead;
Location location;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<const Sector>> sectors;
@@ -23,8 +49,7 @@ struct TrackDataFlux
struct TrackFlux
{
unsigned physicalCylinder;
unsigned physicalHead;
Location location;
std::vector<std::shared_ptr<const TrackDataFlux>> trackDatas;
std::set<std::shared_ptr<const Sector>> sectors;
};

View File

@@ -1,5 +1,6 @@
#include "globals.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "protocol.h"
Fluxmap& Fluxmap::appendBytes(const Bytes& bytes)
@@ -62,39 +63,6 @@ Fluxmap& Fluxmap::appendDesync()
return *this;
}
void Fluxmap::precompensate(int threshold_ticks, int amount_ticks)
{
uint8_t junk = 0xff;
for (unsigned i = 0; i < _bytes.size(); i++)
{
uint8_t& prev = (i == 0) ? junk : _bytes[i - 1];
uint8_t prevticks = prev & 0x3f;
uint8_t currticks = _bytes[i] & 0x3f;
if (currticks < (3 * threshold_ticks))
{
if ((prevticks <= threshold_ticks) && (currticks > threshold_ticks))
{
/* 01001; move the previous bit backwards. */
if (prevticks >= (1 + amount_ticks))
prev -= amount_ticks;
if (currticks <= (0x7f - amount_ticks))
currticks += amount_ticks;
}
else if ((prevticks > threshold_ticks) &&
(currticks <= threshold_ticks))
{
/* 00101; move the current bit forwards. */
if (prevticks <= (0x7f - amount_ticks))
prev += amount_ticks;
if (currticks >= (1 + amount_ticks))
currticks -= amount_ticks;
}
}
}
}
std::vector<std::unique_ptr<const Fluxmap>> Fluxmap::split() const
{
std::vector<std::unique_ptr<const Fluxmap>> maps;
@@ -108,24 +76,3 @@ std::vector<std::unique_ptr<const Fluxmap>> Fluxmap::split() const
return maps;
}
std::unique_ptr<const Fluxmap> Fluxmap::rescale(double scale) const
{
if (scale == 1.0)
return std::make_unique<Fluxmap>(rawBytes());
auto newFluxmap = std::make_unique<Fluxmap>();
int lastEvent = 0;
for (uint8_t b : _bytes)
{
lastEvent += b & 0x3f;
if (b & 0xc0)
{
newFluxmap->appendInterval(lastEvent * scale + 0.5);
newFluxmap->findLastByte() |= b & 0xc0;
lastEvent = 0;
}
}
return newFluxmap;
}

View File

@@ -32,9 +32,10 @@ public:
appendBytes((const uint8_t*) s.c_str(), s.size());
}
Fluxmap(const Bytes bytes):
_bytes(bytes)
{}
Fluxmap(const Bytes bytes)
{
appendBytes(bytes);
}
nanoseconds_t duration() const { return _duration; }
unsigned ticks() const { return _ticks; }
@@ -63,9 +64,8 @@ public:
Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock);
void precompensate(int threshold_ticks, int amount_ticks);
std::unique_ptr<const Fluxmap> precompensate(int threshold_ticks, int amount_ticks);
std::vector<std::unique_ptr<const Fluxmap>> split() const;
std::unique_ptr<const Fluxmap> rescale(double scale) const;
private:
uint8_t& findLastByte();

View File

@@ -37,7 +37,7 @@ public:
Logger() << fmt::format("A2R: writing A2R {} file containing {} tracks\n",
singlesided() ? "single sided" : "double sided",
config.cylinders().end() - config.cylinders().start() + 1
config.tracks().end() - config.tracks().start() + 1
);
time_t now{std::time(nullptr)};
@@ -82,8 +82,7 @@ private:
1,
1,
};
auto version_str = fmt::format("Fluxengine {}", FLUXENGINE_VERSION);
auto version_str_padded = fmt::format("{: <32}", version_str);
auto version_str_padded = fmt::format("{: <32}", "fluxengine");
assert(version_str_padded.size() == sizeof(info.creator));
memcpy(info.creator, version_str_padded.data(), sizeof(info.creator));
writeChunkAndData(A2R_CHUNK_INFO, &info, sizeof(info));

View File

@@ -26,14 +26,14 @@ public:
}
public:
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
unsigned totalTicks = fluxmap.ticks() + 2;
unsigned channels = _config.index_markers() ? 2 : 1;
mkdir(_config.directory().c_str(), 0744);
std::ofstream of(
fmt::format("{}/c{:02d}.h{:01d}.au", _config.directory(), cylinder, head),
fmt::format("{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
std::ios::out | std::ios::binary);
if (!of.is_open())
Error() << "cannot open output file";

View File

@@ -37,7 +37,7 @@ public:
for (const auto& e : _data)
{
auto track = proto.add_track();
track->set_cylinder(e.first.first);
track->set_track(e.first.first);
track->set_head(e.first.second);
for (const auto& fluxBytes : e.second)
track->add_flux(fluxBytes);
@@ -51,9 +51,9 @@ public:
}
public:
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
auto& vector = _data[std::make_pair(cylinder, head)];
auto& vector = _data[std::make_pair(track, head)];
vector.push_back(fluxmap.rawBytes());
}

View File

@@ -29,7 +29,6 @@ message Fl2FluxSinkProto {
// Next: 9
message FluxSinkProto {
optional double rescale = 7 [ default = 1.0, (help) = "amount to multiply pulse periods by" ];
oneof dest {
HardwareFluxSinkProto drive = 2;
A2RFluxSinkProto a2r = 8;

View File

@@ -16,22 +16,27 @@ public:
{
if (config.drive().has_hard_sector_count())
{
nanoseconds_t oneRevolution;
int retries = 5;
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
Logger() << BeginSpeedOperationLogMessage();
do {
oneRevolution = usbGetRotationalPeriod(config.drive().hard_sector_count());
_hardSectorThreshold = oneRevolution * 3 / (4 * config.drive().hard_sector_count());
retries--;
} while ((oneRevolution == 0) && (retries > 0));
nanoseconds_t oneRevolution = config.drive().rotational_period_ms() * 1e6;
if (oneRevolution == 0)
{
Logger() << BeginSpeedOperationLogMessage();
do {
oneRevolution = usbGetRotationalPeriod(config.drive().hard_sector_count());
_hardSectorThreshold = oneRevolution * 3 / (4 * config.drive().hard_sector_count());
retries--;
} while ((oneRevolution == 0) && (retries > 0));
config.mutable_drive()->set_rotational_period_ms(oneRevolution / 1e6);
Logger() << EndSpeedOperationLogMessage { oneRevolution };
}
if (oneRevolution == 0) {
Error() << "Failed\nIs a disk in the drive?";
}
Logger() << EndSpeedOperationLogMessage { oneRevolution };
}
else
_hardSectorThreshold = 0;

View File

@@ -47,8 +47,8 @@ public:
_fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _config.type_byte();
_fileheader.start_track = strackno(config.cylinders().start(), config.heads().start());
_fileheader.end_track = strackno(config.cylinders().end(), config.heads().end());
_fileheader.start_track = strackno(config.tracks().start(), config.heads().start());
_fileheader.end_track = strackno(config.tracks().end(), config.heads().end());
_fileheader.flags = SCP_FLAG_INDEXED
| SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
@@ -80,16 +80,16 @@ public:
}
public:
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
int strack = strackno(cylinder, head);
int strack = strackno(track, head);
if (strack >= std::size(_fileheader.track)) {
std::cout << fmt::format("SCP: cannot write track {} head {}, "
"there are not not enough Track Data Headers.\n",
cylinder, head);
track, head);
return;
}
ScpTrack trackheader = {0};

View File

@@ -20,11 +20,11 @@ public:
{}
public:
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
mkdir(_config.directory().c_str(), 0744);
std::ofstream of(
fmt::format("{}/c{:02d}.h{:01d}.vcd", _config.directory(), cylinder, head),
fmt::format("{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
std::ios::out | std::ios::binary);
if (!of.is_open())
Error() << "cannot open output file";

View File

@@ -14,10 +14,10 @@ struct CwfHeader
uint8_t version; // version of this file
uint8_t clock_rate; // clock rate used: 0, 1, 2 (2 = 28MHz)
uint8_t drive_type; // type of drive
uint8_t cylinders; // number of cylinders
uint8_t tracks; // number of tracks
uint8_t sides; // number of sides
uint8_t index_mark; // nonzero if index marks are included
uint8_t step; // cylinder stepping interval
uint8_t step; // track stepping interval
uint8_t filler[15]; // reserved for expansion
uint8_t comment[100]; // brief comment
};
@@ -58,11 +58,11 @@ public:
Error() << "unsupported clock rate";
}
std::cout << fmt::format("CWF {}x{} = {} cylinders, {} sides\n",
_header.cylinders, _header.step, _header.cylinders*_header.step, _header.sides);
std::cout << fmt::format("CWF {}x{} = {} tracks, {} sides\n",
_header.tracks, _header.step, _header.tracks*_header.step, _header.sides);
std::cout << fmt::format("CWF sample clock rate: {} MHz\n", _clockRate / 1e6);
int tracks = _header.cylinders*_header.sides;
int tracks = _header.tracks*_header.sides;
for (int i=0; i<tracks; i++)
{
CwfTrack trackheader;

View File

@@ -62,12 +62,12 @@ public:
}
public:
std::unique_ptr<FluxSourceIterator> readFlux(int cylinder, int head) override
std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override
{
for (const auto& track : _proto.track())
for (const auto& trackFlux : _proto.track())
{
if ((track.cylinder() == cylinder) && (track.head() == head))
return std::make_unique<Fl2FluxSourceIterator>(track);
if ((trackFlux.track() == track) && (trackFlux.head() == head))
return std::make_unique<Fl2FluxSourceIterator>(trackFlux);
}
return std::make_unique<EmptyFluxSourceIterator>();

View File

@@ -77,9 +77,9 @@ void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::stri
class TrivialFluxSourceIterator : public FluxSourceIterator
{
public:
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int cylinder, int head):
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int track, int head):
_fluxSource(fluxSource),
_cylinder(cylinder),
_track(track),
_head(head)
{}
@@ -90,20 +90,20 @@ public:
std::unique_ptr<const Fluxmap> next() override
{
auto fluxmap = _fluxSource->readSingleFlux(_cylinder, _head);
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
_fluxSource = nullptr;
return fluxmap;
}
private:
TrivialFluxSource* _fluxSource;
int _cylinder;
int _track;
int _head;
};
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int cylinder, int head)
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int track, int head)
{
return std::make_unique<TrivialFluxSourceIterator>(this, cylinder, head);
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
}

View File

@@ -31,7 +31,6 @@ message Fl2FluxSourceProto {
}
message FluxSourceProto {
optional double rescale = 9 [ default = 1.0, (help) = "amount to divide pulse periods by" ];
oneof source {
HardwareFluxSourceProto drive = 2;
TestPatternFluxSourceProto test_pattern = 3;

View File

@@ -15,9 +15,9 @@ private:
{
public:
HardwareFluxSourceIterator(
const HardwareFluxSource& fluxsource, int cylinder, int head):
const HardwareFluxSource& fluxsource, int track, int head):
_fluxsource(fluxsource),
_cylinder(cylinder),
_track(track),
_head(head)
{
}
@@ -30,7 +30,7 @@ private:
std::unique_ptr<const Fluxmap> next()
{
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
usbSeek(_cylinder);
usbSeek(_track);
Bytes data = usbRead(_head,
config.drive().sync_with_index(),
@@ -43,7 +43,7 @@ private:
private:
const HardwareFluxSource& _fluxsource;
int _cylinder;
int _track;
int _head;
};
@@ -54,18 +54,24 @@ public:
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
Logger() << BeginSpeedOperationLogMessage();
do
{
_oneRevolution =
usbGetRotationalPeriod(config.drive().hard_sector_count());
if (config.drive().hard_sector_count() != 0)
_hardSectorThreshold =
_oneRevolution * 3 / (4 * config.drive().hard_sector_count());
else
_hardSectorThreshold = 0;
_oneRevolution = config.drive().rotational_period_ms() * 1e6;
_hardSectorThreshold = 0;
if (_oneRevolution == 0)
{
do
{
_oneRevolution =
usbGetRotationalPeriod(config.drive().hard_sector_count());
if (config.drive().hard_sector_count() != 0)
_hardSectorThreshold =
_oneRevolution * 3 / (4 * config.drive().hard_sector_count());
else
_hardSectorThreshold = 0;
retries--;
} while ((_oneRevolution == 0) && (retries > 0));
retries--;
} while ((_oneRevolution == 0) && (retries > 0));
config.mutable_drive()->set_rotational_period_ms(_oneRevolution / 1e6);
}
if (_oneRevolution == 0)
Error() << "Failed\nIs a disk in the drive?";
@@ -76,10 +82,10 @@ public:
~HardwareFluxSource() {}
public:
std::unique_ptr<FluxSourceIterator> readFlux(int cylinder, int head) override
std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override
{
return std::make_unique<HardwareFluxSourceIterator>(
*this, cylinder, head);
*this, track, head);
}
void recalibrate() override

View File

@@ -14,6 +14,7 @@
#include <cassert>
#include <climits>
#include <variant>
#include <optional>
#if defined(_WIN32) || defined(__WIN32__)
#include <direct.h>

View File

@@ -5,6 +5,7 @@
#include "fmt/format.h"
#include "image.h"
#include "logger.h"
#include "mapper.h"
#include "proto.h"
#include <algorithm>
#include <iostream>
@@ -32,11 +33,11 @@ public:
data.writer() += inputFile;
ByteReader br(data);
unsigned numCylinders = 39;
unsigned numTracks = 39;
unsigned numHeads = 1;
unsigned numSectors = 0;
Logger() << fmt::format("D64: reading image with {} cylinders, {} heads", numCylinders, numHeads);
Logger() << fmt::format("D64: reading image with {} tracks, {} heads", numTracks, numHeads);
uint32_t offset = 0;
@@ -55,7 +56,7 @@ public:
for (int track = 0; track < 40; track++)
{
int numSectors = sectorsPerTrack(track);
int physicalCylinder = track * 2;
int physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
for (int head = 0; head < numHeads; head++)
{
for (int sectorId = 0; sectorId < numSectors; sectorId++)
@@ -69,7 +70,7 @@ public:
sector->status = Sector::OK;
sector->logicalTrack = track;
sector->physicalCylinder = physicalCylinder;
sector->physicalTrack = physicalTrack;
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
sector->data.writer().append(payload);
@@ -79,7 +80,7 @@ public:
// DATA_MISSING
sector->status = Sector::DATA_MISSING;
sector->logicalTrack = track;
sector->physicalCylinder = physicalCylinder;
sector->physicalTrack = physicalTrack;
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
}

View File

@@ -5,6 +5,7 @@
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "mapper.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -62,21 +63,23 @@ public:
Logger() << "D88: overriding configured format";
auto ibm = config.mutable_encoder()->mutable_ibm();
int physicalStep = 1;
int clockRate = 500;
if (mediaFlag == 0x20)
{
Logger() << "D88: high density mode";
if (!config.drive().has_drive())
config.mutable_drive()->set_high_density(true);
if (!config.has_tpi())
config.set_tpi(96);
}
else
{
Logger() << "D88: single/double density mode";
physicalStep = 2;
clockRate = 300;
if (!config.drive().has_drive())
config.mutable_drive()->set_high_density(false);
if (!config.has_tpi())
config.set_tpi(48);
}
std::unique_ptr<Image> image(new Image);
@@ -87,15 +90,15 @@ public:
continue;
int currentTrackOffset = trackOffset;
int currentTrackCylinder = -1;
int currentTrackTrack = -1;
int currentSectorsInTrack =
0xffff; // don't know # of sectors until we read the first one
int trackSectorSize = -1;
int trackMfm = -1;
auto trackdata = ibm->add_trackdata();
trackdata->set_clock_rate_khz(clockRate);
trackdata->set_track_length_ms(167);
trackdata->set_target_clock_period_us(1e3 / clockRate);
trackdata->set_target_rotational_period_ms(167);
auto sectors = trackdata->mutable_sectors();
for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack;
@@ -105,7 +108,7 @@ public:
inputFile.read(
(char*)sectorHeader.begin(), sectorHeader.size());
ByteReader sectorHeaderReader(sectorHeader);
int cylinder = sectorHeaderReader.seek(0).read_8();
int track = sectorHeaderReader.seek(0).read_8();
int head = sectorHeaderReader.seek(1).read_8();
int sectorId = sectorHeaderReader.seek(2).read_8();
int sectorSize = 128 << sectorHeaderReader.seek(3).read_8();
@@ -132,21 +135,21 @@ public:
{
Error() << "D88: mismatched number of sectors in track";
}
if (currentTrackCylinder < 0)
if (currentTrackTrack < 0)
{
currentTrackCylinder = cylinder;
currentTrackTrack = track;
}
else if (currentTrackCylinder != cylinder)
else if (currentTrackTrack != track)
{
Error() << "D88: all sectors in a track must belong to the "
"same cylinder";
"same track";
}
if (trackSectorSize < 0)
{
trackSectorSize = sectorSize;
// this is the first sector we've read, use it settings for
// per-track data
trackdata->set_cylinder(cylinder * physicalStep);
trackdata->set_track(track);
trackdata->set_head(head);
trackdata->set_sector_size(sectorSize);
trackdata->set_use_fm(fm);
@@ -176,11 +179,10 @@ public:
}
Bytes data(sectorSize);
inputFile.read((char*)data.begin(), data.size());
const auto& sector =
image->put(cylinder * physicalStep, head, sectorId);
const auto& sector = image->put(track, head, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = cylinder;
sector->physicalCylinder = cylinder * physicalStep;
sector->logicalTrack = track;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
sector->data = data;
@@ -188,11 +190,11 @@ public:
sectors->add_sector(sectorId);
}
if (physicalStep == 2)
if (mediaFlag != 0x20)
{
auto trackdata = ibm->add_trackdata();
trackdata->set_clock_rate_khz(clockRate);
trackdata->set_track_length_ms(167);
trackdata->set_target_clock_period_us(1e3 / clockRate);
trackdata->set_target_rotational_period_ms(167);
}
}
@@ -209,11 +211,11 @@ public:
heads->set_end(geometry.numSides - 1);
}
if (!config.has_cylinders())
if (!config.has_tracks())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
auto* tracks = config.mutable_tracks();
tracks->set_start(0);
tracks->set_end(geometry.numTracks - 1);
}
return image;

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "logger.h"
#include "mapper.h"
#include "proto.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
@@ -71,7 +72,6 @@ public:
{
if (inputFile.eof())
break;
int physicalCylinder = track;
for (int side = 0; side < 2; side++)
{
@@ -84,11 +84,10 @@ public:
Bytes data(sectorSize);
inputFile.read((char*)data.begin(), data.size());
const auto& sector =
image->put(physicalCylinder, side, sectorId);
const auto& sector = image->put(track, side, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = track;
sector->physicalCylinder = physicalCylinder;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
sector->logicalSide = sector->physicalHead = side;
sector->logicalSector = sectorId;
sector->data = data;
@@ -103,15 +102,15 @@ public:
{
auto ibm = config.mutable_encoder()->mutable_ibm();
auto trackdata = ibm->add_trackdata();
trackdata->set_clock_rate_khz(500);
trackdata->set_target_clock_period_us(2);
auto sectors = trackdata->mutable_sectors();
switch (mediaByte)
{
case 0x00:
Logger() << "DIM: automatically setting format to 1.2MB "
"(1024 byte sectors)";
config.mutable_cylinders()->set_end(76);
trackdata->set_track_length_ms(167);
config.mutable_tracks()->set_end(76);
trackdata->set_target_rotational_period_ms(167);
trackdata->set_sector_size(1024);
for (int i = 0; i < 9; i++)
sectors->add_sector(i);
@@ -119,14 +118,14 @@ public:
case 0x02:
Logger() << "DIM: automatically setting format to 1.2MB "
"(512 byte sectors)";
trackdata->set_track_length_ms(167);
trackdata->set_target_rotational_period_ms(167);
trackdata->set_sector_size(512);
for (int i = 0; i < 15; i++)
sectors->add_sector(i);
break;
case 0x03:
Logger() << "DIM: automatically setting format to 1.44MB";
trackdata->set_track_length_ms(200);
trackdata->set_target_rotational_period_ms(200);
trackdata->set_sector_size(512);
for (int i = 0; i < 18; i++)
sectors->add_sector(i);
@@ -156,11 +155,11 @@ public:
heads->set_end(geometry.numSides - 1);
}
if (!config.has_cylinders())
if (!config.has_tracks())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
auto* tracks = config.mutable_tracks();
tracks->set_start(0);
tracks->set_end(geometry.numTracks - 1);
}
return image;

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "logger.h"
#include "mapper.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -36,7 +37,7 @@ public:
uint8_t encoding = br.read_8();
uint8_t formatByte = br.read_8();
unsigned numCylinders = 80;
unsigned numTracks = 80;
unsigned numHeads = 2;
unsigned numSectors = 0;
bool mfm = false;
@@ -66,8 +67,8 @@ public:
}
Logger() << fmt::format(
"DC42: reading image with {} cylinders, {} heads; {}; {}",
numCylinders,
"DC42: reading image with {} tracks, {} heads; {}; {}",
numTracks,
numHeads,
mfm ? "MFM" : "GCR",
label);
@@ -92,7 +93,7 @@ public:
uint32_t tagPtr = dataPtr + dataSize;
std::unique_ptr<Image> image(new Image);
for (int track = 0; track < numCylinders; track++)
for (int track = 0; track < numTracks; track++)
{
int numSectors = sectorsPerTrack(track);
for (int head = 0; head < numHeads; head++)
@@ -109,7 +110,8 @@ public:
const auto& sector = image->put(track, head, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalCylinder = track;
sector->logicalTrack = track;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
sector->data.writer().append(payload).append(tag);
@@ -117,7 +119,7 @@ public:
}
}
image->setGeometry({.numTracks = numCylinders,
image->setGeometry({.numTracks = numTracks,
.numSides = numHeads,
.numSectors = 12,
.sectorSize = 512 + 12,

View File

@@ -5,6 +5,7 @@
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "mapper.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -50,7 +51,6 @@ public:
{
if (inputFile.eof())
break;
int physicalCylinder = track;
for (int side = 0; side < sides; side++)
{
@@ -64,10 +64,10 @@ public:
inputFile.read((char*)data.begin(), data.size());
const auto& sector =
image->put(physicalCylinder, side, sectorId);
image->put(track, side, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = track;
sector->physicalCylinder = physicalCylinder;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
sector->logicalSide = sector->physicalHead = side;
sector->logicalSector = sectorId;
sector->data = data;
@@ -82,22 +82,22 @@ public:
{
auto ibm = config.mutable_encoder()->mutable_ibm();
auto trackdata = ibm->add_trackdata();
trackdata->set_clock_rate_khz(500);
trackdata->set_target_clock_period_us(2);
auto sectors = trackdata->mutable_sectors();
switch (fddType)
{
case 0x90:
Logger() << "FDI: automatically setting format to 1.2MB "
"(1024 byte sectors)";
config.mutable_cylinders()->set_end(76);
trackdata->set_track_length_ms(167);
config.mutable_tracks()->set_end(76);
trackdata->set_target_rotational_period_ms(167);
trackdata->set_sector_size(1024);
for (int i = 0; i < 9; i++)
sectors->add_sector(i);
break;
case 0x30:
Logger() << "FDI: automatically setting format to 1.44MB";
trackdata->set_track_length_ms(200);
trackdata->set_target_rotational_period_ms(200);
trackdata->set_sector_size(512);
for (int i = 0; i < 18; i++)
sectors->add_sector(i);
@@ -125,11 +125,11 @@ public:
heads->set_end(geometry.numSides - 1);
}
if (!config.has_cylinders())
if (!config.has_tracks())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
auto* tracks = config.mutable_tracks();
tracks->set_start(0);
tracks->set_end(geometry.numTracks - 1);
}
return image;

View File

@@ -34,8 +34,6 @@ message ImgInputOutputProto {
repeated TrackdataProto trackdata = 4 [(help) = "per-track format information (repeatable)"];
optional int32 tracks = 5 [default=0, (help) = "number of tracks in image"];
optional int32 sides = 6 [default=0, (help) = "number of sides in image"];
optional int32 physical_offset = 7 [default=0, (help) = "logical:physical track offset"];
optional int32 physical_step = 8 [default=1, (help) = "logical:physical track step"];
optional Order order = 9 [default=CHS, (help) = "the order in which to emit tracks in the image"];
}

View File

@@ -5,6 +5,7 @@
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "mapper.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -100,12 +101,12 @@ public:
1A byte - ASCII EOF character
- For each track on the disk:
1 byte Mode value see getModulationspeed for definition
1 byte Cylinder
1 byte Track
1 byte Head
1 byte number of sectors in track
1 byte sector size see getsectorsize for definition
sector numbering map
sector cylinder map (optional) definied in high byte of head (since head is 0 or 1)
sector track map (optional) definied in high byte of head (since head is 0 or 1)
sector head map (optional) definied in high byte of head (since head is 0 or 1)
sector data records
<End of file>
@@ -163,7 +164,7 @@ public:
headerPtr++;
sectorSize = getSectorSize(header.SectorSize);
//Read optional cylinder map To Do
//Read optional track map To Do
//Read optional sector head map To Do
@@ -218,7 +219,8 @@ public:
Error() << fmt::format("don't understand IMD disks with sector status {}", Status_Sector);
}
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalCylinder = header.track;
sector->logicalTrack = header.track;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(header.track);
sector->logicalSide = sector->physicalHead = header.Head;
sector->logicalSector = (sector_map[s]);
}

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "logger.h"
#include "mapper.h"
#include "lib/config.pb.h"
#include "imginputoutpututils.h"
#include "fmt/format.h"
@@ -35,8 +36,6 @@ public:
if (inputFile.eof())
break;
int physicalCylinder = track * _config.img().physical_step() +
_config.img().physical_offset();
ImgInputOutputProto::TrackdataProto trackdata;
getTrackFormat(_config.img(), trackdata, track, side);
@@ -46,11 +45,10 @@ public:
Bytes data(trackdata.sector_size());
inputFile.read((char*)data.begin(), data.size());
const auto& sector =
image->put(physicalCylinder, side, sectorId);
const auto& sector = image->put(track, side, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = track;
sector->physicalCylinder = physicalCylinder;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
sector->logicalSide = sector->physicalHead = side;
sector->logicalSector = sectorId;
sector->data = data;

View File

@@ -3,6 +3,7 @@
#include "sector.h"
#include "imagereader/imagereader.h"
#include "image.h"
#include "mapper.h"
#include "logger.h"
#include "fmt/format.h"
#include "lib/config.pb.h"
@@ -123,8 +124,8 @@ public:
const auto& sector =
image->put(header.track, head, header.sector);
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalCylinder =
header.track;
sector->logicalTrack = header.track;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(header.track);
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = header.sector;
sector->data = data;

View File

@@ -5,6 +5,7 @@
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "mapper.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -55,7 +56,7 @@ public:
Logger() << "NFD: overriding configured format";
auto ibm = config.mutable_encoder()->mutable_ibm();
config.mutable_cylinders()->set_end(0);
config.mutable_tracks()->set_end(0);
Logger() << "NFD: HD 1.2MB mode";
if (!config.drive().has_drive())
config.mutable_drive()->set_high_density(true);
@@ -64,17 +65,17 @@ public:
for (int track = 0; track < 163; track++)
{
auto trackdata = ibm->add_trackdata();
trackdata->set_clock_rate_khz(500);
trackdata->set_track_length_ms(167);
trackdata->set_target_clock_period_us(2);
trackdata->set_target_rotational_period_ms(167);
auto sectors = trackdata->mutable_sectors();
int currentTrackCylinder = -1;
int currentTrackTrack = -1;
int currentTrackHead = -1;
int trackSectorSize = -1;
for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++)
{
headerReader.seek(0x120 + track * 26 * 16 + sectorInTrack * 16);
int cylinder = headerReader.read_8();
int track = headerReader.read_8();
int head = headerReader.read_8();
int sectorId = headerReader.read_8();
int sectorSize = 128 << headerReader.read_8();
@@ -82,22 +83,22 @@ public:
int ddam = headerReader.read_8();
int status = headerReader.read_8();
headerReader.skip(9); // skip ST0, ST1, ST2, PDA, reserved(5)
if (cylinder == 0xFF)
if (track == 0xFF)
continue;
if (ddam != 0)
Error() << "NFD: nonzero ddam currently unsupported";
if (status != 0)
Error() << "NFD: nonzero fdd status codes are currently "
"unsupported";
if (currentTrackCylinder < 0)
if (currentTrackTrack < 0)
{
currentTrackCylinder = cylinder;
currentTrackTrack = track;
currentTrackHead = head;
}
else if (currentTrackCylinder != cylinder)
else if (currentTrackTrack != track)
{
Error() << "NFD: all sectors in a track must belong to the "
"same cylinder";
"same track";
}
else if (currentTrackHead != head)
{
@@ -109,7 +110,7 @@ public:
trackSectorSize = sectorSize;
// this is the first sector we've read, use it settings for
// per-track data
trackdata->set_cylinder(cylinder);
trackdata->set_track(track);
trackdata->set_head(head);
trackdata->set_sector_size(sectorSize);
trackdata->set_use_fm(!mfm);
@@ -139,17 +140,17 @@ public:
}
Bytes data(sectorSize);
inputFile.read((char*)data.begin(), data.size());
const auto& sector = image->put(cylinder, head, sectorId);
const auto& sector = image->put(track, head, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = cylinder;
sector->physicalCylinder = cylinder;
sector->logicalTrack = track;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
sector->data = data;
sectors->add_sector(sectorId);
if (config.cylinders().end() < cylinder)
config.mutable_cylinders()->set_end(cylinder);
if (config.tracks().end() < track)
config.mutable_tracks()->set_end(track);
}
}

View File

@@ -7,6 +7,7 @@
#include "image.h"
#include "fmt/format.h"
#include "logger.h"
#include "mapper.h"
#include "lib/imagereader/imagereader.pb.h"
#include <algorithm>
#include <iostream>
@@ -32,7 +33,7 @@ public:
Logger() << fmt::format(
"NSI: Autodetecting geometry based on file size: {}", fsize);
unsigned numCylinders = 35;
unsigned numTracks = 35;
unsigned numSectors = 10;
unsigned numHeads = 2;
unsigned sectorSize = 512;
@@ -63,18 +64,18 @@ public:
Logger() << fmt::format(
"reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} "
"kB total",
numCylinders,
numTracks,
numHeads,
numSectors,
sectorSize,
numCylinders * numHeads * trackSize / 1024);
numTracks * numHeads * trackSize / 1024);
std::unique_ptr<Image> image(new Image);
unsigned sectorFileOffset;
for (unsigned head = 0; head < numHeads; head++)
{
for (unsigned track = 0; track < numCylinders; track++)
for (unsigned track = 0; track < numTracks; track++)
{
for (unsigned sectorId = 0; sectorId < numSectors; sectorId++)
{
@@ -86,8 +87,8 @@ public:
else
{ /* Head 1 is from track 70-35 */
sectorFileOffset =
(trackSize * numCylinders) + /* Skip over side 0 */
((numCylinders - track - 1) * trackSize) +
(trackSize * numTracks) + /* Skip over side 0 */
((numTracks - track - 1) * trackSize) +
(sectorId * sectorSize); /* Sector offset from
beginning of track. */
}
@@ -99,7 +100,8 @@ public:
const auto& sector = image->put(track, head, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalCylinder = track;
sector->logicalTrack = track;
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
sector->data = data;
@@ -107,7 +109,7 @@ public:
}
}
image->setGeometry({.numTracks = numCylinders,
image->setGeometry({.numTracks = numTracks,
.numSides = numHeads,
.numSectors = numSectors,
.sectorSize = sectorSize});

View File

@@ -5,6 +5,7 @@
#include "image.h"
#include "crc.h"
#include "logger.h"
#include "mapper.h"
#include "fmt/format.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
@@ -111,7 +112,7 @@ public:
if (sectorCount == 0xff)
break;
uint8_t physicalCylinder = br.read_8();
uint8_t physicalTrack = br.read_8();
uint8_t physicalHead = br.read_8() & 1;
br.skip(1); /* crc */
@@ -185,7 +186,7 @@ public:
const auto& sector =
image->put(logicalTrack, logicalSide, sectorId);
sector->status = Sector::OK;
sector->physicalCylinder = physicalCylinder;
sector->physicalTrack = physicalTrack;
sector->physicalHead = physicalHead;
sector->data = data.slice(0, sectorSize);
totalSize += sectorSize;

View File

@@ -47,7 +47,7 @@ public:
if (sector)
{
outputFile.seekp(offset);
outputFile.write((const char*) sector->data.cbegin(), 256);
outputFile.write((const char*) sector->data.cbegin(), sector->data.size());
}
offset += 256;

View File

@@ -97,7 +97,7 @@ void ImageWriter::writeCsv(const Image& image, const std::string& filename)
for (const auto& sector : image)
{
f << fmt::format("{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
sector->physicalCylinder,
sector->physicalTrack,
sector->physicalHead,
sector->logicalTrack,
sector->logicalSide,

View File

@@ -31,7 +31,7 @@ public:
return;
}
Logger() << fmt::format("Writing {} cylinders, {} sides, {} sectors, {} ({} bytes/sector), {} kB total",
Logger() << fmt::format("Writing {} tracks, {} sides, {} sectors, {} ({} bytes/sector), {} kB total",
geometry.numTracks, geometry.numSides,
geometry.numSectors, geometry.sectorSize == 256 ? "SD" : "DD", geometry.sectorSize,
geometry.numTracks * geometry.numSides * geometry.numSectors * geometry.sectorSize / 1024);

View File

@@ -30,7 +30,7 @@ public:
return;
}
Logger() << fmt::format("RAW: writing {} cylinders, {} sides",
Logger() << fmt::format("RAW: writing {} tracks, {} sides",
geometry.numTracks, geometry.numSides);
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);

View File

@@ -62,14 +62,14 @@ std::string Logger::toString(const AnyLogMessage& message)
/* Indicates that we're starting a write operation. */
[&](const BeginWriteOperationLogMessage& m)
{
stream << fmt::format("{:2}.{}: ", m.cylinder, m.head);
stream << fmt::format("{:2}.{}: ", m.track, m.head);
indented = true;
},
/* Indicates that we're starting a read operation. */
[&](const BeginReadOperationLogMessage& m)
{
stream << fmt::format("{:2}.{}: ", m.cylinder, m.head);
stream << fmt::format("{:2}.{}: ", m.track, m.head);
indented = true;
},
@@ -78,16 +78,27 @@ std::string Logger::toString(const AnyLogMessage& message)
[&](const TrackReadLogMessage& m)
{
const auto& track = *m.track;
const auto& trackdataflux = track.trackDatas.end()[-1];
std::set<std::shared_ptr<const Sector>> rawSectors;
std::set<std::shared_ptr<const Record>> rawRecords;
for (const auto& trackDataFlux : track.trackDatas)
{
rawSectors.insert(trackDataFlux->sectors.begin(), trackDataFlux->sectors.end());
rawRecords.insert(trackDataFlux->records.begin(), trackDataFlux->records.end());
}
nanoseconds_t clock = 0;
for (const auto& sector : rawSectors)
clock += sector->clock;
if (!rawSectors.empty())
clock /= rawSectors.size();
indent();
stream << fmt::format("{} raw records, {} raw sectors",
trackdataflux->records.size(),
trackdataflux->sectors.size());
if (trackdataflux->sectors.size() > 0)
rawRecords.size(),
rawSectors.size());
if (clock != 0)
{
nanoseconds_t clock =
(*trackdataflux->sectors.begin())->clock;
stream << fmt::format("; {:.2f}us clock ({:.0f}kHz)",
clock / 1000.0,
1000000.0 / clock);
@@ -104,7 +115,9 @@ std::string Logger::toString(const AnyLogMessage& message)
sectors.begin(), sectors.end(), sectorPointerSortPredicate);
for (const auto& sector : sectors)
stream << fmt::format(" {}{}",
stream << fmt::format(" {}.{}.{}{}",
sector->logicalTrack,
sector->logicalSide,
sector->logicalSector,
Sector::statusToChar(sector->status));
@@ -119,18 +132,6 @@ std::string Logger::toString(const AnyLogMessage& message)
size += sector->data.size();
}
if (!track_ids.empty())
{
std::vector<std::string> ids;
for (const auto& i : track_ids)
ids.push_back(fmt::format("{}.{}", i.first, i.second));
indent();
stream << fmt::format(
"logical track {}\n", fmt::join(ids, "; "));
}
indent();
stream << fmt::format("{} bytes decoded\n", size);
},

View File

@@ -34,7 +34,7 @@ struct DiskReadLogMessage
struct BeginReadOperationLogMessage
{
unsigned cylinder;
unsigned track;
unsigned head;
};
@@ -46,7 +46,7 @@ struct EndReadOperationLogMessage
struct BeginWriteOperationLogMessage
{
unsigned cylinder;
unsigned track;
unsigned head;
};

View File

@@ -3,89 +3,159 @@
#include "image.h"
#include "fmt/format.h"
#include "logger.h"
#include "proto.h"
#include "mapper.h"
#include "flux.h"
#include "lib/mapper.pb.h"
typedef std::function<void(std::map<int, int>&, const SectorMappingProto::MappingProto&)> insertercb_t;
typedef std::function<void(
std::map<int, int>&, const SectorMappingProto::MappingProto&)>
insertercb_t;
static void getTrackFormat(const SectorMappingProto& proto,
SectorMappingProto::TrackdataProto& trackdata, unsigned track, unsigned side)
SectorMappingProto::TrackdataProto& trackdata,
unsigned track,
unsigned side)
{
trackdata.Clear();
for (const SectorMappingProto::TrackdataProto& f : proto.trackdata())
{
if (f.has_track() && f.has_up_to_track() && ((track < f.track()) || (track > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() && (track != f.track()))
continue;
if (f.has_side() && (f.side() != side))
continue;
trackdata.Clear();
for (const SectorMappingProto::TrackdataProto& f : proto.trackdata())
{
if (f.has_track() && f.has_up_to_track() &&
((track < f.track()) || (track > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() && (track != f.track()))
continue;
if (f.has_side() && (f.side() != side))
continue;
trackdata.MergeFrom(f);
}
trackdata.MergeFrom(f);
}
}
static std::unique_ptr<Image> remapImpl(const Image& source, const SectorMappingProto& mapping,
insertercb_t inserter_cb)
static std::unique_ptr<Image> remapImpl(const Image& source,
const SectorMappingProto& mapping,
insertercb_t inserter_cb)
{
typedef std::pair<int, int> tracksidekey_t;
std::map<tracksidekey_t, std::map<int, int>> cache;
typedef std::pair<int, int> tracksidekey_t;
std::map<tracksidekey_t, std::map<int, int>> cache;
auto getTrackdata =
[&](const tracksidekey_t& key) -> const std::map<int, int>& {
auto it = cache.find(key);
if (it != cache.end())
return it->second;
auto getTrackdata =
[&](const tracksidekey_t& key) -> const std::map<int, int>&
{
auto it = cache.find(key);
if (it != cache.end())
return it->second;
SectorMappingProto::TrackdataProto trackdata;
getTrackFormat(mapping, trackdata, key.first, key.second);
SectorMappingProto::TrackdataProto trackdata;
getTrackFormat(mapping, trackdata, key.first, key.second);
auto& map = cache[key];
for (const auto mappingsit : trackdata.mapping())
inserter_cb(map, mappingsit);
auto& map = cache[key];
for (const auto mappingsit : trackdata.mapping())
inserter_cb(map, mappingsit);
return map;
};
return map;
};
std::set<std::shared_ptr<const Sector>> destSectors;
for (const auto& sector : source)
{
tracksidekey_t key = { sector->logicalTrack, sector->logicalSide };
const auto& trackdata = getTrackdata(key);
if (trackdata.empty())
destSectors.insert(sector);
else
{
auto it = trackdata.find(sector->logicalSector);
if (it == trackdata.end())
Error() << fmt::format("mapping requested but mapping table has no entry for sector {}", sector->logicalSector);
std::set<std::shared_ptr<const Sector>> destSectors;
for (const auto& sector : source)
{
tracksidekey_t key = {sector->logicalTrack, sector->logicalSide};
const auto& trackdata = getTrackdata(key);
if (trackdata.empty())
destSectors.insert(sector);
else
{
auto it = trackdata.find(sector->logicalSector);
if (it == trackdata.end())
Error() << fmt::format(
"mapping requested but mapping table has no entry for "
"sector {}",
sector->logicalSector);
auto newSector = std::make_shared<Sector>(*sector);
newSector->logicalSector = it->second;
destSectors.insert(newSector);
}
}
auto newSector = std::make_shared<Sector>(*sector);
newSector->logicalSector = it->second;
destSectors.insert(newSector);
}
}
return std::make_unique<Image>(destSectors);
return std::make_unique<Image>(destSectors);
}
std::unique_ptr<Image> Mapper::remapPhysicalToLogical(const Image& source, const SectorMappingProto& mapping)
std::unique_ptr<const Image> Mapper::remapSectorsPhysicalToLogical(
const Image& source, const SectorMappingProto& mapping)
{
Logger() << "remapping sectors from physical IDs to logical IDs";
return remapImpl(source, mapping,
[](auto& map, const auto& pair)
{
map.insert({ pair.physical(), pair.logical() });
});
Logger() << "remapping sectors from physical IDs to logical IDs";
return remapImpl(source,
mapping,
[](auto& map, const auto& pair)
{
map.insert({pair.physical(), pair.logical()});
});
}
std::unique_ptr<Image> Mapper::remapLogicalToPhysical(const Image& source, const SectorMappingProto& mapping)
std::unique_ptr<const Image> Mapper::remapSectorsLogicalToPhysical(
const Image& source, const SectorMappingProto& mapping)
{
Logger() << "remapping sectors from logical IDs to physical IDs";
return remapImpl(source, mapping,
[](auto& map, const auto& pair)
{
map.insert({ pair.logical(), pair.physical() });
});
Logger() << "remapping sectors from logical IDs to physical IDs";
return remapImpl(source,
mapping,
[](auto& map, const auto& pair)
{
map.insert({pair.logical(), pair.physical()});
});
}
unsigned Mapper::remapTrackPhysicalToLogical(unsigned ptrack)
{
return (ptrack - config.drive().head_bias()) / config.drive().head_width();
}
static unsigned getTrackStep()
{
unsigned track_step =
(config.tpi() == 0) ? 1 : (config.drive().tpi() / config.tpi());
if (track_step == 0)
Error()
<< "this drive can't write this image, because the head is too big";
return track_step;
}
unsigned Mapper::remapTrackLogicalToPhysical(unsigned ltrack)
{
return config.drive().head_bias() + ltrack * getTrackStep();
}
std::set<Location> Mapper::computeLocations()
{
std::set<Location> locations;
unsigned track_step = getTrackStep();
for (unsigned logicalTrack : iterate(config.tracks()))
{
for (unsigned head : iterate(config.heads()))
{
unsigned physicalTrack =
config.drive().head_bias() + logicalTrack * track_step;
locations.insert({.physicalTrack = physicalTrack,
.logicalTrack = logicalTrack,
.head = head,
.groupSize = track_step});
}
}
return locations;
}
nanoseconds_t Mapper::calculatePhysicalClockPeriod(
nanoseconds_t targetClockPeriod, nanoseconds_t targetRotationalPeriod)
{
nanoseconds_t currentRotationalPeriod =
config.drive().rotational_period_ms() * 1e6;
if (currentRotationalPeriod == 0)
Error() << "you must set --drive.rotational_period_ms as it can't be "
"autodetected";
return targetClockPeriod * (currentRotationalPeriod / targetRotationalPeriod);
}

View File

@@ -2,13 +2,24 @@
#define MAPPER_H
class SectorMappingProto;
class DriveProto;
class Location;
class Mapper
{
public:
static std::unique_ptr<Image> remapPhysicalToLogical(const Image& source, const SectorMappingProto& mapping);
static std::unique_ptr<Image> remapLogicalToPhysical(const Image& source, const SectorMappingProto& mapping);
static std::unique_ptr<const Image> remapSectorsPhysicalToLogical(
const Image& source, const SectorMappingProto& mapping);
static std::unique_ptr<const Image> remapSectorsLogicalToPhysical(
const Image& source, const SectorMappingProto& mapping);
static unsigned remapTrackPhysicalToLogical(unsigned track);
static unsigned remapTrackLogicalToPhysical(unsigned track);
static std::set<Location> computeLocations();
static nanoseconds_t calculatePhysicalClockPeriod(
nanoseconds_t targetClockPeriod, nanoseconds_t targetRotationalPeriod);
};
#endif

View File

@@ -1,271 +0,0 @@
#include "globals.h"
#include "flags.h"
#include "usb/usb.h"
#include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h"
#include "reader.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "bytes.h"
#include "decoders/rawbits.h"
#include "flux.h"
#include "image.h"
#include "imagewriter/imagewriter.h"
#include "logger.h"
#include "fmt/format.h"
#include "proto.h"
#include "utils.h"
#include "mapper.h"
#include "lib/decoders/decoders.pb.h"
#include <iostream>
#include <fstream>
static std::unique_ptr<FluxSink> outputFluxSink;
static std::shared_ptr<const Fluxmap> readFluxmap(FluxSourceIterator& fluxsourceIterator, unsigned cylinder, unsigned head)
{
Logger() << BeginReadOperationLogMessage { cylinder, head };
auto fluxmap = fluxsourceIterator.next()->rescale(1.0/config.flux_source().rescale());
Logger() << EndReadOperationLogMessage()
<< fmt::format("{0:.0} ms in {1} bytes", fluxmap->duration()/1e6, fluxmap->bytes());
return fluxmap;
}
static bool conflictable(Sector::Status status)
{
return (status == Sector::OK) || (status == Sector::CONFLICT);
}
static std::set<std::shared_ptr<const Sector>> collect_sectors(
std::set<std::shared_ptr<const Sector>>& track_sectors, bool collapse_conflicts = true)
{
typedef std::tuple<unsigned, unsigned, unsigned> key_t;
std::multimap<key_t, std::shared_ptr<const Sector>> sectors;
for (const auto& sector : track_sectors)
{
key_t sectorid = {
sector->logicalTrack, sector->logicalSide, sector->logicalSector};
sectors.insert({sectorid, sector});
}
std::set<std::shared_ptr<const Sector>> sector_set;
auto it = sectors.begin();
while (it != sectors.end())
{
auto ub = sectors.upper_bound(it->first);
auto new_sector = std::accumulate(it,
ub,
it->second,
[&](auto left, auto& rightit) -> std::shared_ptr<const Sector>
{
auto& right = rightit.second;
if ((left->status == Sector::OK) &&
(right->status == Sector::OK) &&
(left->data != right->data))
{
if (!collapse_conflicts)
{
auto s = std::make_shared<Sector>(*right);
s->status = Sector::CONFLICT;
sector_set.insert(s);
}
auto s = std::make_shared<Sector>(*left);
s->status = Sector::CONFLICT;
return s;
}
if (left->status == Sector::CONFLICT)
return left;
if (right->status == Sector::CONFLICT)
return right;
if (left->status == Sector::OK)
return left;
if (right->status == Sector::OK)
return right;
return left;
});
sector_set.insert(new_sector);
it = ub;
}
return sector_set;
}
std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder)
{
if (config.decoder().has_copy_flux_to())
outputFluxSink = FluxSink::create(config.decoder().copy_flux_to());
auto diskflux = std::make_shared<DiskFlux>();
bool failures = false;
for (int cylinder : iterate(config.cylinders()))
{
for (int head : iterate(config.heads()))
{
testForEmergencyStop();
auto track = std::make_shared<TrackFlux>();
track->physicalCylinder = cylinder;
track->physicalHead = head;
diskflux->tracks.push_back(track);
std::set<std::shared_ptr<const Sector>> track_sectors;
std::set<std::shared_ptr<const Record>> track_records;
Fluxmap totalFlux;
auto fluxsourceIterator = fluxsource.readFlux(cylinder, head);
int retriesRemaining = config.decoder().retries();
while (fluxsourceIterator->hasNext())
{
auto fluxmap = readFluxmap(*fluxsourceIterator, cylinder, head);
totalFlux.appendDesync().appendBytes(fluxmap->rawBytes());
auto trackdataflux =
decoder.decodeToSectors(fluxmap, cylinder, head);
track->trackDatas.push_back(trackdataflux);
track_sectors.insert(trackdataflux->sectors.begin(),
trackdataflux->sectors.end());
track_records.insert(trackdataflux->records.begin(),
trackdataflux->records.end());
bool hasBadSectors = false;
std::set<unsigned> required_sectors =
decoder.requiredSectors(cylinder, head);
std::set<std::shared_ptr<const Sector>> result_sectors;
for (const auto& sector : collect_sectors(track_sectors))
{
result_sectors.insert(sector);
required_sectors.erase(sector->logicalSector);
if (sector->status != Sector::OK)
hasBadSectors = true;
}
for (unsigned logical_sector : required_sectors)
{
auto sector = std::make_shared<Sector>();
sector->logicalSector = logical_sector;
sector->status = Sector::MISSING;
result_sectors.insert(sector);
hasBadSectors = true;
}
track->sectors = collect_sectors(result_sectors);
/* track can't be modified below this point. */
Logger() << TrackReadLogMessage { track };
if (hasBadSectors)
failures = false;
if (!hasBadSectors)
break;
if (!fluxsourceIterator->hasNext())
break;
if (fluxsource.isHardware())
{
retriesRemaining--;
if (retriesRemaining == 0)
{
Logger() << fmt::format("giving up");
break;
}
else
Logger()
<< fmt::format("retrying; {} retries remaining", retriesRemaining);
}
}
if (outputFluxSink)
outputFluxSink->writeFlux(cylinder, head, totalFlux);
if (config.decoder().dump_records())
{
std::vector<std::shared_ptr<const Record>> sorted_records(track_records.begin(), track_records.end());
std::sort(sorted_records.begin(), sorted_records.end(),
[](const auto& o1, const auto& o2) {
return o1->startTime < o2->startTime;
});
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record->startTime / 1000.0,
record->clock / 1000.0);
hexdump(std::cout, record->rawData);
std::cout << std::endl;
}
}
if (config.decoder().dump_sectors())
{
auto collected_sectors = collect_sectors(track_sectors, false);
std::vector<std::shared_ptr<const Sector>> sorted_sectors(collected_sectors.begin(), collected_sectors.end());
std::sort(sorted_sectors.begin(), sorted_sectors.end(),
[](const auto& o1, const auto& o2) {
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sorted_sectors)
{
std::cout << fmt::format(
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n",
sector->logicalTrack,
sector->logicalSide,
sector->logicalSector,
sector->headerStartTime / 1000.0,
sector->clock / 1000.0,
Sector::statusToString(sector->status));
hexdump(std::cout, sector->data);
std::cout << std::endl;
}
}
}
}
if (failures)
Logger() << "Warning: some sectors could not be decoded.";
std::set<std::shared_ptr<const Sector>> all_sectors;
for (auto& track : diskflux->tracks)
for (auto& sector : track->sectors)
all_sectors.insert(sector);
all_sectors = collect_sectors(all_sectors);
diskflux->image = std::make_shared<Image>(all_sectors);
if (config.has_sector_mapping())
diskflux->image = std::move(Mapper::remapPhysicalToLogical(*diskflux->image, config.sector_mapping()));
/* diskflux can't be modified below this point. */
Logger() << DiskReadLogMessage { diskflux };
return diskflux;
}
void readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer)
{
auto diskflux = readDiskCommand(fluxsource, decoder);
writer.printMap(*diskflux->image);
if (config.decoder().has_write_csv_to())
writer.writeCsv(*diskflux->image, config.decoder().write_csv_to());
writer.writeImage(*diskflux->image);
}
void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
{
for (int cylinder : iterate(config.cylinders()))
{
for (int head : iterate(config.heads()))
{
testForEmergencyStop();
auto fluxsourceIterator = fluxsource.readFlux(cylinder, head);
auto fluxmap = readFluxmap(*fluxsourceIterator, cylinder, head);
fluxsink.writeFlux(cylinder, head, *fluxmap);
}
}
}

View File

@@ -1,19 +0,0 @@
#ifndef READER_H
#define READER_H
class AbstractDecoder;
class DiskFlux;
class FluxSink;
class FluxSource;
class Fluxmap;
class ImageWriter;
class TrackDataFlux;
extern std::unique_ptr<TrackDataFlux> readAndDecodeTrack(
FluxSource& source, AbstractDecoder& decoder, unsigned cylinder, unsigned head);
extern std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder);
extern void readDiskCommand(FluxSource& source, AbstractDecoder& decoder, ImageWriter& writer);
extern void rawReadDiskCommand(FluxSource& source, FluxSink& sink);
#endif

508
lib/readerwriter.cc Normal file
View File

@@ -0,0 +1,508 @@
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "readerwriter.h"
#include "protocol.h"
#include "usb/usb.h"
#include "encoders/encoders.h"
#include "decoders/decoders.h"
#include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h"
#include "imagereader/imagereader.h"
#include "imagewriter/imagewriter.h"
#include "fmt/format.h"
#include "sector.h"
#include "image.h"
#include "logger.h"
#include "mapper.h"
#include "utils.h"
#include "lib/config.pb.h"
#include "proto.h"
#include <optional>
enum ReadResult
{
GOOD_READ,
BAD_AND_CAN_RETRY,
BAD_AND_CAN_NOT_RETRY
};
/* In order to allow rereads in file-based flux sources, we need to persist the
* FluxSourceIterator (as that's where the state for which read to return is
* held). This class handles that. */
class FluxSourceIteratorHolder
{
public:
FluxSourceIteratorHolder(FluxSource& fluxSource): _fluxSource(fluxSource) {}
FluxSourceIterator& getIterator(unsigned physicalCylinder, unsigned head)
{
auto& it = _cache[std::make_pair(physicalCylinder, head)];
if (!it)
it = _fluxSource.readFlux(physicalCylinder, head);
return *it;
}
private:
FluxSource& _fluxSource;
std::map<std::pair<unsigned, unsigned>, std::unique_ptr<FluxSourceIterator>>
_cache;
};
/* Given a set of sectors, deduplicates them sensibly (e.g. if there is a good
* and bad version of the same sector, the bad version is dropped). */
static std::set<std::shared_ptr<const Sector>> collectSectors(
std::set<std::shared_ptr<const Sector>>& track_sectors,
bool collapse_conflicts = true)
{
typedef std::tuple<unsigned, unsigned, unsigned> key_t;
std::multimap<key_t, std::shared_ptr<const Sector>> sectors;
for (const auto& sector : track_sectors)
{
key_t sectorid = {
sector->logicalTrack, sector->logicalSide, sector->logicalSector};
sectors.insert({sectorid, sector});
}
std::set<std::shared_ptr<const Sector>> sector_set;
auto it = sectors.begin();
while (it != sectors.end())
{
auto ub = sectors.upper_bound(it->first);
auto new_sector = std::accumulate(it,
ub,
it->second,
[&](auto left, auto& rightit) -> std::shared_ptr<const Sector>
{
auto& right = rightit.second;
if ((left->status == Sector::OK) &&
(right->status == Sector::OK) &&
(left->data != right->data))
{
if (!collapse_conflicts)
{
auto s = std::make_shared<Sector>(*right);
s->status = Sector::CONFLICT;
sector_set.insert(s);
}
auto s = std::make_shared<Sector>(*left);
s->status = Sector::CONFLICT;
return s;
}
if (left->status == Sector::CONFLICT)
return left;
if (right->status == Sector::CONFLICT)
return right;
if (left->status == Sector::OK)
return left;
if (right->status == Sector::OK)
return right;
return left;
});
sector_set.insert(new_sector);
it = ub;
}
return sector_set;
}
/* Returns true if the result contains bad sectors. */
bool combineRecordAndSectors(
TrackFlux& trackFlux, AbstractDecoder& decoder, const Location& location)
{
std::set<std::shared_ptr<const Sector>> track_sectors;
for (auto& trackdataflux : trackFlux.trackDatas)
track_sectors.insert(
trackdataflux->sectors.begin(), trackdataflux->sectors.end());
for (unsigned logical_sector : decoder.requiredSectors(trackFlux.location))
{
auto sector = std::make_shared<Sector>(location);
sector->logicalSector = logical_sector;
sector->status = Sector::MISSING;
track_sectors.insert(sector);
}
trackFlux.sectors = collectSectors(track_sectors);
if (trackFlux.sectors.empty())
return true;
for (const auto& sector : trackFlux.sectors)
if (sector->status != Sector::OK)
return true;
return false;
}
ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder,
const Location& location,
TrackFlux& trackFlux,
AbstractDecoder& decoder)
{
ReadResult result = BAD_AND_CAN_NOT_RETRY;
for (unsigned offset = 0; offset < location.groupSize;
offset += config.drive().head_width())
{
auto& fluxSourceIterator = fluxSourceIteratorHolder.getIterator(
location.physicalTrack + offset, location.head);
if (!fluxSourceIterator.hasNext())
continue;
Logger() << BeginReadOperationLogMessage{
location.physicalTrack + offset, location.head};
std::shared_ptr<const Fluxmap> fluxmap = fluxSourceIterator.next();
// ->rescale(
// 1.0 / config.flux_source().rescale());
Logger() << EndReadOperationLogMessage()
<< fmt::format("{0:.0} ms in {1} bytes",
fluxmap->duration() / 1e6,
fluxmap->bytes());
auto trackdataflux = decoder.decodeToSectors(fluxmap, location);
trackFlux.trackDatas.push_back(trackdataflux);
if (!combineRecordAndSectors(trackFlux, decoder, location))
return GOOD_READ;
if (fluxSourceIterator.hasNext())
result = BAD_AND_CAN_RETRY;
}
return result;
}
void writeTracks(FluxSink& fluxSink,
std::function<std::unique_ptr<const Fluxmap>(const Location& location)>
producer,
std::function<bool(const Location& location)> verifier)
{
Logger() << fmt::format("Writing to: {}", (std::string)fluxSink);
for (const auto& location : Mapper::computeLocations())
{
testForEmergencyStop();
int retriesRemaining = config.decoder().retries();
for (;;)
{
for (int offset = 0; offset < location.groupSize;
offset += config.drive().head_width())
{
unsigned physicalTrack = location.physicalTrack + offset;
Logger() << BeginWriteOperationLogMessage{
physicalTrack, location.head};
if (offset == 0)
{
auto fluxmap = producer(location);
if (!fluxmap)
goto erase;
fluxSink.writeFlux(physicalTrack, location.head, *fluxmap);
Logger() << fmt::format("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
else
{
erase:
/* Erase this track rather than writing. */
Fluxmap blank;
fluxSink.writeFlux(physicalTrack, location.head, blank);
Logger() << "erased";
}
Logger() << EndWriteOperationLogMessage();
}
if (verifier(location))
break;
if (retriesRemaining == 0)
Error() << "fatal error on write";
Logger() << fmt::format(
"retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
}
}
static bool dontVerify(const Location&)
{
return true;
}
void writeTracks(
FluxSink& fluxSink, AbstractEncoder& encoder, const Image& image)
{
writeTracks(
fluxSink,
[&](const Location& location)
{
auto sectors = encoder.collectSectors(location, image);
return encoder.encode(location, sectors, image);
},
dontVerify);
}
void writeTracksAndVerify(FluxSink& fluxSink,
AbstractEncoder& encoder,
FluxSource& fluxSource,
AbstractDecoder& decoder,
const Image& image)
{
writeTracks(
fluxSink,
[&](const Location& location)
{
auto sectors = encoder.collectSectors(location, image);
return encoder.encode(location, sectors, image);
},
[&](const Location& location)
{
auto trackFlux = std::make_shared<TrackFlux>();
trackFlux->location = location;
FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource);
auto result = readGroup(
fluxSourceIteratorHolder, location, *trackFlux, decoder);
Logger() << TrackReadLogMessage{trackFlux};
if (result != GOOD_READ)
{
Logger() << "bad read";
return false;
}
auto wantedSectors = encoder.collectSectors(location, image);
std::sort(wantedSectors.begin(),
wantedSectors.end(),
sectorPointerSortPredicate);
std::vector<std::shared_ptr<const Sector>> gotSectors(
trackFlux->sectors.begin(), trackFlux->sectors.end());
std::sort(gotSectors.begin(),
gotSectors.end(),
sectorPointerSortPredicate);
if (!std::equal(gotSectors.begin(),
gotSectors.end(),
wantedSectors.begin(),
wantedSectors.end(),
sectorPointerEqualsPredicate))
{
Logger() << "good read but the data doesn't match";
return false;
}
return true;
});
}
void writeDiskCommand(std::shared_ptr<const Image> image,
AbstractEncoder& encoder,
FluxSink& fluxSink,
AbstractDecoder* decoder,
FluxSource* fluxSource)
{
if (config.has_sector_mapping())
image = std::move(Mapper::remapSectorsLogicalToPhysical(
*image, config.sector_mapping()));
if (fluxSource && decoder)
writeTracksAndVerify(fluxSink, encoder, *fluxSource, *decoder, *image);
else
writeTracks(fluxSink, encoder, *image);
}
void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink)
{
writeTracks(
fluxSink,
[&](const Location& location)
{
return fluxSource.readFlux(location.physicalTrack, location.head)
->next();
},
dontVerify);
}
std::shared_ptr<const DiskFlux> readDiskCommand(
FluxSource& fluxSource, AbstractDecoder& decoder)
{
std::unique_ptr<FluxSink> outputFluxSink;
if (config.decoder().has_copy_flux_to())
outputFluxSink = FluxSink::create(config.decoder().copy_flux_to());
auto diskflux = std::make_shared<DiskFlux>();
bool failures = false;
FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource);
for (const auto& location : Mapper::computeLocations())
{
testForEmergencyStop();
auto trackFlux = std::make_shared<TrackFlux>();
trackFlux->location = location;
diskflux->tracks.push_back(trackFlux);
int retriesRemaining = config.decoder().retries();
for (;;)
{
auto result = readGroup(
fluxSourceIteratorHolder, location, *trackFlux, decoder);
if (result == GOOD_READ)
break;
if (result == BAD_AND_CAN_NOT_RETRY)
{
failures = true;
Logger() << fmt::format("no more data; giving up");
break;
}
if (retriesRemaining == 0)
{
failures = true;
Logger() << fmt::format("giving up");
break;
}
Logger() << fmt::format(
"retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
if (outputFluxSink)
{
for (const auto& data : trackFlux->trackDatas)
outputFluxSink->writeFlux(
location.physicalTrack, location.head, *data->fluxmap);
}
if (config.decoder().dump_records())
{
std::vector<std::shared_ptr<const Record>> sorted_records;
for (const auto& data : trackFlux->trackDatas)
sorted_records.insert(sorted_records.end(),
data->records.begin(),
data->records.end());
std::sort(sorted_records.begin(),
sorted_records.end(),
[](const auto& o1, const auto& o2)
{
return o1->startTime < o2->startTime;
});
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record->startTime / 1000.0,
record->clock / 1000.0);
hexdump(std::cout, record->rawData);
std::cout << std::endl;
}
}
if (config.decoder().dump_sectors())
{
auto collected_sectors = collectSectors(trackFlux->sectors, false);
std::vector<std::shared_ptr<const Sector>> sorted_sectors(
collected_sectors.begin(), collected_sectors.end());
std::sort(sorted_sectors.begin(),
sorted_sectors.end(),
[](const auto& o1, const auto& o2)
{
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sorted_sectors)
{
std::cout << fmt::format(
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n",
sector->logicalTrack,
sector->logicalSide,
sector->logicalSector,
sector->headerStartTime / 1000.0,
sector->clock / 1000.0,
Sector::statusToString(sector->status));
hexdump(std::cout, sector->data);
std::cout << std::endl;
}
}
/* track can't be modified below this point. */
Logger() << TrackReadLogMessage{trackFlux};
}
if (failures)
Logger() << "Warning: some sectors could not be decoded.";
std::set<std::shared_ptr<const Sector>> all_sectors;
for (auto& track : diskflux->tracks)
for (auto& sector : track->sectors)
all_sectors.insert(sector);
all_sectors = collectSectors(all_sectors);
diskflux->image = std::make_shared<Image>(all_sectors);
if (config.has_sector_mapping())
diskflux->image = std::move(Mapper::remapSectorsPhysicalToLogical(
*diskflux->image, config.sector_mapping()));
/* diskflux can't be modified below this point. */
Logger() << DiskReadLogMessage{diskflux};
return diskflux;
}
void readDiskCommand(
FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer)
{
auto diskflux = readDiskCommand(fluxsource, decoder);
writer.printMap(*diskflux->image);
if (config.decoder().has_write_csv_to())
writer.writeCsv(*diskflux->image, config.decoder().write_csv_to());
writer.writeImage(*diskflux->image);
}
void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
{
for (unsigned track : iterate(config.tracks()))
{
for (unsigned head : iterate(config.heads()))
{
testForEmergencyStop();
auto fluxSourceIterator = fluxsource.readFlux(track, head);
Logger() << BeginReadOperationLogMessage{track, head};
auto fluxmap = fluxSourceIterator->next();
Logger() << EndReadOperationLogMessage()
<< fmt::format("{0:.0} ms in {1} bytes",
fluxmap->duration() / 1e6,
fluxmap->bytes());
fluxsink.writeFlux(track, head, *fluxmap);
}
}
}
void fillBitmapTo(std::vector<bool>& bitmap,
unsigned& cursor,
unsigned terminateAt,
const std::vector<bool>& pattern)
{
while (cursor < terminateAt)
{
for (bool b : pattern)
{
if (cursor < bitmap.size())
bitmap[cursor++] = b;
}
}
}

View File

@@ -1,16 +1,20 @@
#ifndef WRITER_H
#define WRITER_H
class Fluxmap;
class AbstractDecoder;
class AbstractEncoder;
class ImageReader;
class FluxSource;
class DiskFlux;
class FluxSink;
class FluxSource;
class Fluxmap;
class Image;
class ImageReader;
class ImageWriter;
class Location;
class TrackDataFlux;
extern void writeTracks(FluxSink& fluxSink,
const std::function<std::unique_ptr<const Fluxmap>(int track, int side)>
const std::function<std::unique_ptr<const Fluxmap>(const Location& location)>
producer);
extern void fillBitmapTo(std::vector<bool>& bitmap,
@@ -26,4 +30,11 @@ extern void writeDiskCommand(std::shared_ptr<const Image> image,
extern void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink);
extern std::unique_ptr<TrackDataFlux> readAndDecodeTrack(
FluxSource& source, AbstractDecoder& decoder, unsigned track, unsigned head);
extern std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder);
extern void readDiskCommand(FluxSource& source, AbstractDecoder& decoder, ImageWriter& writer);
extern void rawReadDiskCommand(FluxSource& source, FluxSink& sink);
#endif

View File

@@ -1,7 +1,15 @@
#include "globals.h"
#include "flux.h"
#include "sector.h"
#include "fmt/format.h"
Sector::Sector(const Location& location):
physicalTrack(location.physicalTrack),
physicalHead(location.head),
logicalTrack(location.logicalTrack),
logicalSide(location.head)
{}
std::string Sector::statusToString(Status status)
{
switch (status)
@@ -55,14 +63,14 @@ Sector::Status Sector::stringToStatus(const std::string& value)
return Status::INTERNAL_ERROR;
}
bool sectorPointerSortPredicate(
std::shared_ptr<const Sector>& lhs, std::shared_ptr<const Sector>& rhs)
bool sectorPointerSortPredicate(const std::shared_ptr<const Sector>& lhs,
const std::shared_ptr<const Sector>& rhs)
{
return *lhs < *rhs;
}
bool sectorPointerEqualsPredicate(
std::shared_ptr<const Sector>& lhs, std::shared_ptr<const Sector>& rhs)
bool sectorPointerEqualsPredicate(const std::shared_ptr<const Sector>& lhs,
const std::shared_ptr<const Sector>& rhs)
{
if (!lhs && !rhs)
return true;

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