Merge remote-tracking branch 'origin/master' into applea2r
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
20
README.md
@@ -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) | 🦄 | 🦄 | |
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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" ];
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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."];
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
struct IbmIdam
|
||||
{
|
||||
uint8_t id;
|
||||
uint8_t cylinder;
|
||||
uint8_t track;
|
||||
uint8_t side;
|
||||
uint8_t sector;
|
||||
uint8_t sectorSize;
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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."];
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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" ];
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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" ];
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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" ];
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
```
|
||||
|
||||
@@ -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
@@ -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
|
After Width: | Height: | Size: 23 KiB |
BIN
doc/ju475-dd-lo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
doc/mpf90-dd-lo.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
doc/mpf90-hd-hi.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
doc/mpf90-hd-lo.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 62 KiB |
@@ -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
|
||||
|
||||
15
doc/using.md
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
10
lib/flags.h
@@ -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>
|
||||
|
||||
33
lib/flux.h
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
|
||||
#if defined(_WIN32) || defined(__WIN32__)
|
||||
#include <direct.h>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
192
lib/mapper.cc
@@ -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);
|
||||
}
|
||||
|
||||
17
lib/mapper.h
@@ -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
|
||||
|
||||
|
||||
271
lib/reader.cc
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
lib/reader.h
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||