From ed8898964251c58c31d8ba3ac3aa4c04572a6b4c Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 23 Jul 2018 23:25:43 +0100 Subject: [PATCH] Refactor image classes and split them to smaller files. --- .../.idea/contentModel.xml | 575 +- .../Alcohol120/Alcohol120.cs | 88 + .../Alcohol120/Constants.cs | 40 + DiscImageChef.DiscImages/Alcohol120/Enums.cs | 69 + .../Alcohol120/Helpers.cs | 157 + .../Alcohol120/Identify.cs | 59 + .../Alcohol120/Properties.cs | 141 + .../{Alcohol120.cs => Alcohol120/Read.cs} | 1334 +--- .../Alcohol120/Structs.cs | 120 + DiscImageChef.DiscImages/Alcohol120/Write.cs | 985 +++ DiscImageChef.DiscImages/Anex86.cs | 551 -- DiscImageChef.DiscImages/Anex86/Anex86.cs | 75 + DiscImageChef.DiscImages/Anex86/Identify.cs | 71 + DiscImageChef.DiscImages/Anex86/Properties.cs | 90 + DiscImageChef.DiscImages/Anex86/Read.cs | 129 + DiscImageChef.DiscImages/Anex86/Structs.cs | 52 + .../Anex86/Unsupported.cs | 119 + DiscImageChef.DiscImages/Anex86/Write.cs | 269 + DiscImageChef.DiscImages/Apple2MG.cs | 852 --- DiscImageChef.DiscImages/Apple2MG/Apple2MG.cs | 76 + .../Apple2MG/Constants.cs | 80 + DiscImageChef.DiscImages/Apple2MG/Enums.cs | 44 + DiscImageChef.DiscImages/Apple2MG/Helpers.cs | 54 + DiscImageChef.DiscImages/Apple2MG/Identify.cs | 75 + .../Apple2MG/Properties.cs | 80 + DiscImageChef.DiscImages/Apple2MG/Read.cs | 298 + DiscImageChef.DiscImages/Apple2MG/Structs.cs | 114 + .../Apple2MG/Unsupported.cs | 138 + DiscImageChef.DiscImages/Apple2MG/Write.cs | 257 + DiscImageChef.DiscImages/AppleDOS.cs | 447 -- DiscImageChef.DiscImages/AppleDOS/AppleDOS.cs | 75 + .../AppleDOS/Constants.cs | 40 + DiscImageChef.DiscImages/AppleDOS/Identify.cs | 47 + .../AppleDOS/Properties.cs | 73 + DiscImageChef.DiscImages/AppleDOS/Read.cs | 103 + .../AppleDOS/Unsupported.cs | 144 + DiscImageChef.DiscImages/AppleDOS/Write.cs | 210 + DiscImageChef.DiscImages/AppleNIB/AppleNIB.cs | 75 + .../AppleNIB/Constants.cs | 53 + DiscImageChef.DiscImages/AppleNIB/Helpers.cs | 49 + DiscImageChef.DiscImages/AppleNIB/Identify.cs | 54 + .../AppleNIB/Properties.cs | 62 + .../{AppleNIB.cs => AppleNIB/Read.cs} | 176 +- .../AppleNIB/Unsupported.cs | 118 + DiscImageChef.DiscImages/Apridisk.cs | 772 --- DiscImageChef.DiscImages/Apridisk/Apridisk.cs | 77 + .../Apridisk/Compression.cs | 64 + .../Apridisk/Constants.cs | 49 + DiscImageChef.DiscImages/Apridisk/Enums.cs | 51 + DiscImageChef.DiscImages/Apridisk/Helpers.cs | 46 + DiscImageChef.DiscImages/Apridisk/Identify.cs | 54 + .../Apridisk/Properties.cs | 83 + DiscImageChef.DiscImages/Apridisk/Read.cs | 256 + DiscImageChef.DiscImages/Apridisk/Structs.cs | 51 + .../Apridisk/Unsupported.cs | 138 + DiscImageChef.DiscImages/Apridisk/Write.cs | 307 + DiscImageChef.DiscImages/BLU.cs | 782 --- DiscImageChef.DiscImages/BLU/BLU.cs | 84 + DiscImageChef.DiscImages/BLU/Constants.cs | 42 + DiscImageChef.DiscImages/BLU/Identify.cs | 66 + DiscImageChef.DiscImages/BLU/Properties.cs | 72 + DiscImageChef.DiscImages/BLU/Read.cs | 250 + DiscImageChef.DiscImages/BLU/Unsupported.cs | 108 + DiscImageChef.DiscImages/BLU/Write.cs | 404 ++ .../BlindWrite4/BlindWrite4.cs | 77 + .../BlindWrite4/Constants.cs | 44 + DiscImageChef.DiscImages/BlindWrite4/Enums.cs | 44 + .../BlindWrite4/Identify.cs | 53 + .../BlindWrite4/Properties.cs | 58 + .../{BlindWrite4.cs => BlindWrite4/Read.cs} | 200 +- .../BlindWrite4/Structs.cs | 151 + .../BlindWrite5/BlindWrite5.cs | 90 + .../BlindWrite5/Constants.cs | 44 + DiscImageChef.DiscImages/BlindWrite5/Enums.cs | 55 + .../BlindWrite5/Helpers.cs | 97 + .../BlindWrite5/Identify.cs | 57 + .../BlindWrite5/Properties.cs | 59 + .../{BlindWrite5.cs => BlindWrite5/Read.cs} | 293 +- .../BlindWrite5/Structs.cs | 166 + DiscImageChef.DiscImages/CDRDAO/CDRDAO.cs | 84 + DiscImageChef.DiscImages/CDRDAO/Constants.cs | 90 + DiscImageChef.DiscImages/CDRDAO/Helpers.cs | 114 + DiscImageChef.DiscImages/CDRDAO/Identify.cs | 98 + DiscImageChef.DiscImages/CDRDAO/Properties.cs | 124 + .../{CDRDAO.cs => CDRDAO/Read.cs} | 1075 +--- DiscImageChef.DiscImages/CDRDAO/Structs.cs | 139 + .../CDRDAO/Unsupported.cs | 42 + DiscImageChef.DiscImages/CDRDAO/Write.cs | 698 +++ DiscImageChef.DiscImages/CDRWin/CDRWin.cs | 83 + DiscImageChef.DiscImages/CDRWin/Constants.cs | 161 + DiscImageChef.DiscImages/CDRWin/Helpers.cs | 242 + DiscImageChef.DiscImages/CDRWin/Identify.cs | 105 + DiscImageChef.DiscImages/CDRWin/Properties.cs | 129 + .../{CDRWin.cs => CDRWin/Read.cs} | 1089 +--- DiscImageChef.DiscImages/CDRWin/Structs.cs | 132 + .../CDRWin/Unsupported.cs | 42 + DiscImageChef.DiscImages/CDRWin/Write.cs | 508 ++ DiscImageChef.DiscImages/CHD/CHD.cs | 100 + DiscImageChef.DiscImages/CHD/Constants.cs | 90 + DiscImageChef.DiscImages/CHD/Enums.cs | 88 + DiscImageChef.DiscImages/CHD/Helpers.cs | 176 + DiscImageChef.DiscImages/CHD/Identify.cs | 51 + DiscImageChef.DiscImages/CHD/Properties.cs | 85 + .../{CHD.cs => CHD/Read.cs} | 777 +-- DiscImageChef.DiscImages/CHD/Structs.cs | 443 ++ DiscImageChef.DiscImages/CPCDSK/CPCDSK.cs | 74 + DiscImageChef.DiscImages/CPCDSK/Constants.cs | 66 + DiscImageChef.DiscImages/CPCDSK/Helpers.cs | 55 + DiscImageChef.DiscImages/CPCDSK/Identify.cs | 65 + DiscImageChef.DiscImages/CPCDSK/Properties.cs | 57 + .../{CPCDSK.cs => CPCDSK/Read.cs} | 341 +- DiscImageChef.DiscImages/CPCDSK/Structs.cs | 168 + .../CPCDSK/Unsupported.cs | 128 + DiscImageChef.DiscImages/CisCopy.cs | 707 --- DiscImageChef.DiscImages/CisCopy/CisCopy.cs | 90 + DiscImageChef.DiscImages/CisCopy/Enums.cs | 65 + DiscImageChef.DiscImages/CisCopy/Identify.cs | 111 + .../CisCopy/Properties.cs | 72 + DiscImageChef.DiscImages/CisCopy/Read.cs | 204 + .../CisCopy/Unsupported.cs | 144 + DiscImageChef.DiscImages/CisCopy/Write.cs | 263 + DiscImageChef.DiscImages/CloneCD/CloneCD.cs | 84 + DiscImageChef.DiscImages/CloneCD/Constants.cs | 68 + DiscImageChef.DiscImages/CloneCD/Helpers.cs | 52 + DiscImageChef.DiscImages/CloneCD/Identify.cs | 90 + .../CloneCD/Properties.cs | 76 + .../{CloneCD.cs => CloneCD/Read.cs} | 745 +-- .../CloneCD/Unsupported.cs | 42 + DiscImageChef.DiscImages/CloneCD/Write.cs | 602 ++ DiscImageChef.DiscImages/CopyQM.cs | 501 -- DiscImageChef.DiscImages/CopyQM/Constants.cs | 81 + DiscImageChef.DiscImages/CopyQM/CopyQM.cs | 77 + DiscImageChef.DiscImages/CopyQM/Identify.cs | 55 + DiscImageChef.DiscImages/CopyQM/Properties.cs | 57 + DiscImageChef.DiscImages/CopyQM/Read.cs | 216 + DiscImageChef.DiscImages/CopyQM/Structs.cs | 109 + .../CopyQM/Unsupported.cs | 139 + DiscImageChef.DiscImages/D88/Constants.cs | 40 + DiscImageChef.DiscImages/D88/D88.cs | 75 + DiscImageChef.DiscImages/D88/Enums.cs | 88 + DiscImageChef.DiscImages/D88/Identify.cs | 91 + DiscImageChef.DiscImages/D88/Properties.cs | 57 + .../{D88.cs => D88/Read.cs} | 352 +- DiscImageChef.DiscImages/D88/Structs.cs | 134 + DiscImageChef.DiscImages/D88/Unsupported.cs | 138 + DiscImageChef.DiscImages/DART/Constants.cs | 73 + DiscImageChef.DiscImages/DART/DART.cs | 76 + DiscImageChef.DiscImages/DART/Identify.cs | 92 + DiscImageChef.DiscImages/DART/Properties.cs | 57 + .../{DART.cs => DART/Read.cs} | 236 +- DiscImageChef.DiscImages/DART/Structs.cs | 47 + DiscImageChef.DiscImages/DART/Unsupported.cs | 118 + DiscImageChef.DiscImages/DIM/Constants.cs | 41 + DiscImageChef.DiscImages/DIM/DIM.cs | 76 + DiscImageChef.DiscImages/DIM/Enums.cs | 47 + DiscImageChef.DiscImages/DIM/Identify.cs | 60 + DiscImageChef.DiscImages/DIM/Properties.cs | 57 + .../{DIM.cs => DIM/Read.cs} | 194 +- DiscImageChef.DiscImages/DIM/Unsupported.cs | 138 + .../DiscFerret/Constants.cs | 46 + .../DiscFerret/DiscFerret.cs | 74 + .../DiscFerret/Identify.cs | 51 + .../DiscFerret/Properties.cs | 57 + .../{DiscFerret.cs => DiscFerret/Read.cs} | 143 +- .../DiscFerret/Structs.cs | 48 + .../DiscFerret/Unsupported.cs | 98 + .../DiscImageChef.DiscImages.csproj | 473 +- DiscImageChef.DiscImages/DiscImageChef.cs | 5504 ----------------- .../ClauniaSubchannelTransform.cs | 296 + .../DiscImageChef/Constants.cs | 60 + .../DiscImageChef/DiscImageChef.cs | 188 + .../DiscImageChef/Enums.cs | 247 + .../DiscImageChef/Helpers.cs | 407 ++ .../DiscImageChef/Identify.cs | 59 + .../DiscImageChef/Properties.cs | 79 + .../DiscImageChef/Read.cs | 1423 +++++ .../DiscImageChef/Structs.cs | 326 + .../DiscImageChef/Verify.cs | 321 + .../DiscImageChef/Write.cs | 2528 ++++++++ .../DiscJuggler/DiscJuggler.cs | 89 + .../DiscJuggler/Helpers.cs | 50 + .../DiscJuggler/Identify.cs | 73 + .../DiscJuggler/Properties.cs | 54 + .../{DiscJuggler.cs => DiscJuggler/Read.cs} | 98 +- DiscImageChef.DiscImages/DiskCopy42.cs | 1199 ---- .../DiskCopy42/Constants.cs | 82 + .../DiskCopy42/DiskCopy42.cs | 94 + .../DiskCopy42/Helpers.cs | 52 + .../DiskCopy42/Identify.cs | 124 + .../DiskCopy42/Properties.cs | 76 + DiscImageChef.DiscImages/DiskCopy42/Read.cs | 511 ++ .../DiskCopy42/Structs.cs | 60 + .../DiskCopy42/Unsupported.cs | 119 + DiscImageChef.DiscImages/DiskCopy42/Write.cs | 403 ++ DiscImageChef.DiscImages/DriDiskCopy.cs | 661 -- .../DriDiskCopy/Constants.cs | 39 + .../DriDiskCopy/DriDiskCopy.cs | 76 + DiscImageChef.DiscImages/DriDiskCopy/Enums.cs | 57 + .../DriDiskCopy/Identify.cs | 99 + .../DriDiskCopy/Properties.cs | 82 + DiscImageChef.DiscImages/DriDiskCopy/Read.cs | 147 + .../DriDiskCopy/Structs.cs | 99 + .../DriDiskCopy/Unsupported.cs | 144 + DiscImageChef.DiscImages/DriDiskCopy/Write.cs | 245 + DiscImageChef.DiscImages/GDI/Constants.cs | 42 + DiscImageChef.DiscImages/GDI/GDI.cs | 94 + DiscImageChef.DiscImages/GDI/Identify.cs | 110 + DiscImageChef.DiscImages/GDI/Properties.cs | 93 + .../{GDI.cs => GDI/Read.cs} | 207 +- DiscImageChef.DiscImages/GDI/Structs.cs | 80 + DiscImageChef.DiscImages/GDI/Unsupported.cs | 46 + DiscImageChef.DiscImages/HDCopy.cs | 471 -- DiscImageChef.DiscImages/HDCopy/HDCopy.cs | 123 + DiscImageChef.DiscImages/HDCopy/Helpers.cs | 83 + DiscImageChef.DiscImages/HDCopy/Identify.cs | 79 + DiscImageChef.DiscImages/HDCopy/Properties.cs | 63 + DiscImageChef.DiscImages/HDCopy/Read.cs | 152 + DiscImageChef.DiscImages/HDCopy/Structs.cs | 83 + .../HDCopy/Unsupported.cs | 139 + DiscImageChef.DiscImages/IMD/Constants.cs | 43 + DiscImageChef.DiscImages/IMD/Enums.cs | 66 + DiscImageChef.DiscImages/IMD/IMD.cs | 72 + DiscImageChef.DiscImages/IMD/Identify.cs | 57 + DiscImageChef.DiscImages/IMD/Properties.cs | 61 + .../{IMD.cs => IMD/Read.cs} | 214 +- DiscImageChef.DiscImages/IMD/Unsupported.cs | 144 + .../KryoFlux/Constants.cs | 48 + DiscImageChef.DiscImages/KryoFlux/Enums.cs | 65 + DiscImageChef.DiscImages/KryoFlux/Identify.cs | 72 + DiscImageChef.DiscImages/KryoFlux/KryoFlux.cs | 74 + .../KryoFlux/Properties.cs | 59 + .../{KryoFlux.cs => KryoFlux/Read.cs} | 194 +- DiscImageChef.DiscImages/KryoFlux/Structs.cs | 47 + .../KryoFlux/Unsupported.cs | 98 + DiscImageChef.DiscImages/MaxiDisk.cs | 605 -- DiscImageChef.DiscImages/MaxiDisk/Enums.cs | 53 + DiscImageChef.DiscImages/MaxiDisk/Identify.cs | 88 + DiscImageChef.DiscImages/MaxiDisk/MaxiDisk.cs | 73 + .../MaxiDisk/Properties.cs | 83 + DiscImageChef.DiscImages/MaxiDisk/Read.cs | 121 + DiscImageChef.DiscImages/MaxiDisk/Structs.cs | 52 + .../MaxiDisk/Unsupported.cs | 144 + DiscImageChef.DiscImages/MaxiDisk/Write.cs | 280 + DiscImageChef.DiscImages/NDIF/Constants.cs | 67 + DiscImageChef.DiscImages/NDIF/Identify.cs | 59 + DiscImageChef.DiscImages/NDIF/NDIF.cs | 83 + DiscImageChef.DiscImages/NDIF/Properties.cs | 61 + .../{NDIF.cs => NDIF/Read.cs} | 324 +- DiscImageChef.DiscImages/NDIF/Structs.cs | 145 + DiscImageChef.DiscImages/NDIF/Unsupported.cs | 138 + DiscImageChef.DiscImages/NHDr0.cs | 559 -- DiscImageChef.DiscImages/NHDr0/Constants.cs | 40 + DiscImageChef.DiscImages/NHDr0/Identify.cs | 78 + DiscImageChef.DiscImages/NHDr0/NHDr0.cs | 76 + DiscImageChef.DiscImages/NHDr0/Properties.cs | 72 + DiscImageChef.DiscImages/NHDr0/Read.cs | 106 + DiscImageChef.DiscImages/NHDr0/Structs.cs | 58 + DiscImageChef.DiscImages/NHDr0/Unsupported.cs | 138 + DiscImageChef.DiscImages/NHDr0/Write.cs | 282 + DiscImageChef.DiscImages/Nero/Constants.cs | 53 + DiscImageChef.DiscImages/Nero/Enums.cs | 223 + DiscImageChef.DiscImages/Nero/Helpers.cs | 111 + DiscImageChef.DiscImages/Nero/Identify.cs | 73 + DiscImageChef.DiscImages/Nero/Nero.cs | 86 + DiscImageChef.DiscImages/Nero/Properties.cs | 53 + .../{Nero.cs => Nero/Read.cs} | 956 +-- DiscImageChef.DiscImages/Nero/Structs.cs | 596 ++ DiscImageChef.DiscImages/Parallels.cs | 670 -- .../Parallels/Constants.cs | 49 + .../Parallels/Identify.cs | 61 + .../Parallels/Parallels.cs | 82 + .../Parallels/Properties.cs | 78 + DiscImageChef.DiscImages/Parallels/Read.cs | 166 + DiscImageChef.DiscImages/Parallels/Structs.cs | 93 + .../Parallels/Unsupported.cs | 138 + DiscImageChef.DiscImages/Parallels/Write.cs | 291 + DiscImageChef.DiscImages/PartClone.cs | 435 -- .../PartClone/Constants.cs | 44 + DiscImageChef.DiscImages/PartClone/Helpers.cs | 44 + .../PartClone/Identify.cs | 68 + .../PartClone/PartClone.cs | 81 + .../PartClone/Properties.cs | 62 + DiscImageChef.DiscImages/PartClone/Read.cs | 191 + DiscImageChef.DiscImages/PartClone/Structs.cs | 87 + .../PartClone/Unsupported.cs | 139 + .../Partimage/Constants.cs | 71 + DiscImageChef.DiscImages/Partimage/Enums.cs | 50 + DiscImageChef.DiscImages/Partimage/Helpers.cs | 44 + .../Partimage/Identify.cs | 61 + .../Partimage/Partimage.cs | 82 + .../Partimage/Properties.cs | 63 + .../{Partimage.cs => Partimage/Read.cs} | 405 +- DiscImageChef.DiscImages/Partimage/Structs.cs | 206 + .../Partimage/Unsupported.cs | 139 + DiscImageChef.DiscImages/QCOW.cs | 820 --- DiscImageChef.DiscImages/QCOW/Constants.cs | 49 + DiscImageChef.DiscImages/QCOW/Identify.cs | 54 + DiscImageChef.DiscImages/QCOW/Properties.cs | 77 + DiscImageChef.DiscImages/QCOW/QCOW.cs | 89 + DiscImageChef.DiscImages/QCOW/Read.cs | 278 + DiscImageChef.DiscImages/QCOW/Structs.cs | 91 + DiscImageChef.DiscImages/QCOW/Unsupported.cs | 138 + DiscImageChef.DiscImages/QCOW/Write.cs | 326 + DiscImageChef.DiscImages/QCOW2.cs | 908 --- DiscImageChef.DiscImages/QCOW2/Constants.cs | 64 + DiscImageChef.DiscImages/QCOW2/Identify.cs | 59 + DiscImageChef.DiscImages/QCOW2/Properties.cs | 78 + DiscImageChef.DiscImages/QCOW2/QCOW2.cs | 90 + DiscImageChef.DiscImages/QCOW2/Read.cs | 288 + DiscImageChef.DiscImages/QCOW2/Structs.cs | 106 + DiscImageChef.DiscImages/QCOW2/Unsupported.cs | 138 + DiscImageChef.DiscImages/QCOW2/Write.cs | 372 ++ DiscImageChef.DiscImages/QED/Constants.cs | 66 + DiscImageChef.DiscImages/QED/Enums.cs | 39 + DiscImageChef.DiscImages/QED/Helpers.cs | 38 + DiscImageChef.DiscImages/QED/Identify.cs | 61 + DiscImageChef.DiscImages/QED/Properties.cs | 60 + DiscImageChef.DiscImages/{ => QED}/QED.cs | 264 +- DiscImageChef.DiscImages/QED/Read.cs | 244 + DiscImageChef.DiscImages/QED/Structs.cs | 38 + DiscImageChef.DiscImages/QED/Unsupported.cs | 38 + DiscImageChef.DiscImages/QED/Write.cs | 38 + DiscImageChef.DiscImages/RayDIM.cs | 561 -- DiscImageChef.DiscImages/RayDIM/Constants.cs | 40 + DiscImageChef.DiscImages/RayDIM/Enums.cs | 49 + DiscImageChef.DiscImages/RayDIM/Identify.cs | 77 + DiscImageChef.DiscImages/RayDIM/Properties.cs | 82 + DiscImageChef.DiscImages/RayDIM/RayDIM.cs | 72 + DiscImageChef.DiscImages/RayDIM/Read.cs | 130 + DiscImageChef.DiscImages/RayDIM/Structs.cs | 50 + .../RayDIM/Unsupported.cs | 144 + DiscImageChef.DiscImages/RayDIM/Write.cs | 245 + DiscImageChef.DiscImages/RsIde.cs | 655 -- DiscImageChef.DiscImages/RsIde/Constants.cs | 39 + DiscImageChef.DiscImages/RsIde/Enums.cs | 45 + DiscImageChef.DiscImages/RsIde/Helpers.cs | 66 + DiscImageChef.DiscImages/RsIde/Identify.cs | 52 + DiscImageChef.DiscImages/RsIde/Properties.cs | 77 + DiscImageChef.DiscImages/RsIde/Read.cs | 139 + DiscImageChef.DiscImages/RsIde/RsIde.cs | 76 + DiscImageChef.DiscImages/RsIde/Structs.cs | 53 + DiscImageChef.DiscImages/RsIde/Unsupported.cs | 133 + DiscImageChef.DiscImages/RsIde/Write.cs | 345 ++ DiscImageChef.DiscImages/SaveDskF.cs | 624 -- .../SaveDskF/Constants.cs | 41 + DiscImageChef.DiscImages/SaveDskF/Identify.cs | 63 + .../SaveDskF/Properties.cs | 83 + DiscImageChef.DiscImages/SaveDskF/Read.cs | 162 + DiscImageChef.DiscImages/SaveDskF/SaveDskF.cs | 76 + DiscImageChef.DiscImages/SaveDskF/Structs.cs | 84 + .../SaveDskF/Unsupported.cs | 139 + DiscImageChef.DiscImages/SaveDskF/Write.cs | 267 + .../SuperCardPro/Constants.cs | 50 + .../SuperCardPro/Enums.cs | 94 + .../SuperCardPro/Helpers.cs | 58 + .../SuperCardPro/Identify.cs | 61 + .../SuperCardPro/Properties.cs | 63 + .../{SuperCardPro.cs => SuperCardPro/Read.cs} | 271 +- .../SuperCardPro/Structs.cs | 92 + .../SuperCardPro/SuperCardPro.cs | 76 + .../SuperCardPro/Unsupported.cs | 93 + DiscImageChef.DiscImages/T98.cs | 452 -- DiscImageChef.DiscImages/T98/Identify.cs | 65 + DiscImageChef.DiscImages/T98/Properties.cs | 76 + DiscImageChef.DiscImages/T98/Read.cs | 101 + DiscImageChef.DiscImages/T98/T98.cs | 74 + DiscImageChef.DiscImages/T98/Unsupported.cs | 138 + DiscImageChef.DiscImages/T98/Write.cs | 208 + .../TeleDisk/Constants.cs | 99 + DiscImageChef.DiscImages/TeleDisk/Helpers.cs | 313 + DiscImageChef.DiscImages/TeleDisk/Identify.cs | 98 + .../TeleDisk/Properties.cs | 63 + .../{TeleDisk.cs => TeleDisk/Read.cs} | 585 +- DiscImageChef.DiscImages/TeleDisk/Structs.cs | 111 + DiscImageChef.DiscImages/TeleDisk/TeleDisk.cs | 85 + .../TeleDisk/Unsupported.cs | 92 + DiscImageChef.DiscImages/UDIF.cs | 1164 ---- DiscImageChef.DiscImages/UDIF/Constants.cs | 74 + DiscImageChef.DiscImages/UDIF/Identify.cs | 65 + DiscImageChef.DiscImages/UDIF/Properties.cs | 78 + DiscImageChef.DiscImages/UDIF/Read.cs | 510 ++ DiscImageChef.DiscImages/UDIF/Structs.cs | 110 + DiscImageChef.DiscImages/UDIF/UDIF.cs | 87 + DiscImageChef.DiscImages/UDIF/Unsupported.cs | 138 + DiscImageChef.DiscImages/UDIF/Write.cs | 393 ++ DiscImageChef.DiscImages/UkvFdi/Constants.cs | 39 + DiscImageChef.DiscImages/UkvFdi/Enums.cs | 57 + DiscImageChef.DiscImages/UkvFdi/Helpers.cs | 46 + DiscImageChef.DiscImages/UkvFdi/Identify.cs | 61 + DiscImageChef.DiscImages/UkvFdi/Properties.cs | 61 + .../{UkvFdi.cs => UkvFdi/Read.cs} | 217 +- DiscImageChef.DiscImages/UkvFdi/Structs.cs | 52 + DiscImageChef.DiscImages/UkvFdi/UkvFdi.cs | 73 + .../UkvFdi/Unsupported.cs | 138 + DiscImageChef.DiscImages/VDI.cs | 726 --- DiscImageChef.DiscImages/VDI/Constants.cs | 52 + DiscImageChef.DiscImages/VDI/Enums.cs | 63 + DiscImageChef.DiscImages/VDI/Identify.cs | 60 + DiscImageChef.DiscImages/VDI/Properties.cs | 79 + DiscImageChef.DiscImages/VDI/Read.cs | 193 + DiscImageChef.DiscImages/VDI/Structs.cs | 80 + DiscImageChef.DiscImages/VDI/Unsupported.cs | 138 + DiscImageChef.DiscImages/VDI/VDI.cs | 81 + DiscImageChef.DiscImages/VDI/Write.cs | 308 + DiscImageChef.DiscImages/VHD/Constants.cs | 173 + DiscImageChef.DiscImages/VHD/Helpers.cs | 47 + DiscImageChef.DiscImages/VHD/Identify.cs | 62 + DiscImageChef.DiscImages/VHD/Properties.cs | 91 + .../{VHD.cs => VHD/Read.cs} | 777 +-- DiscImageChef.DiscImages/VHD/Structs.cs | 200 + DiscImageChef.DiscImages/VHD/Unsupported.cs | 138 + DiscImageChef.DiscImages/VHD/VHD.cs | 90 + DiscImageChef.DiscImages/VHD/Write.cs | 297 + DiscImageChef.DiscImages/VHDX/Constants.cs | 90 + DiscImageChef.DiscImages/VHDX/Helpers.cs | 58 + DiscImageChef.DiscImages/VHDX/Identify.cs | 60 + DiscImageChef.DiscImages/VHDX/Properties.cs | 63 + .../{VHDX.cs => VHDX/Read.cs} | 476 +- DiscImageChef.DiscImages/VHDX/Structs.cs | 241 + DiscImageChef.DiscImages/VHDX/Unsupported.cs | 138 + DiscImageChef.DiscImages/VHDX/VHDX.cs | 100 + DiscImageChef.DiscImages/VMware/Constants.cs | 93 + DiscImageChef.DiscImages/VMware/Identify.cs | 86 + DiscImageChef.DiscImages/VMware/Properties.cs | 84 + .../{VMware.cs => VMware/Read.cs} | 662 +- DiscImageChef.DiscImages/VMware/Structs.cs | 107 + .../VMware/Unsupported.cs | 138 + DiscImageChef.DiscImages/VMware/VMware.cs | 93 + DiscImageChef.DiscImages/VMware/Write.cs | 343 + DiscImageChef.DiscImages/Virtual98.cs | 574 -- .../Virtual98/Constants.cs | 39 + .../Virtual98/Identify.cs | 79 + .../Virtual98/Properties.cs | 77 + DiscImageChef.DiscImages/Virtual98/Read.cs | 113 + DiscImageChef.DiscImages/Virtual98/Structs.cs | 57 + .../Virtual98/Unsupported.cs | 138 + .../Virtual98/Virtual98.cs | 76 + DiscImageChef.DiscImages/Virtual98/Write.cs | 285 + .../ZZZRawImage/Constants.cs | 68 + .../ZZZRawImage/Helpers.cs | 155 + .../ZZZRawImage/Identify.cs | 91 + .../ZZZRawImage/Properties.cs | 171 + .../{ZZZRawImage.cs => ZZZRawImage/Read.cs} | 554 +- DiscImageChef.DiscImages/ZZZRawImage/Write.cs | 228 + .../ZZZRawImage/ZZZRawImage.cs | 81 + 445 files changed, 50086 insertions(+), 34342 deletions(-) create mode 100644 DiscImageChef.DiscImages/Alcohol120/Alcohol120.cs create mode 100644 DiscImageChef.DiscImages/Alcohol120/Constants.cs create mode 100644 DiscImageChef.DiscImages/Alcohol120/Enums.cs create mode 100644 DiscImageChef.DiscImages/Alcohol120/Helpers.cs create mode 100644 DiscImageChef.DiscImages/Alcohol120/Identify.cs create mode 100644 DiscImageChef.DiscImages/Alcohol120/Properties.cs rename DiscImageChef.DiscImages/{Alcohol120.cs => Alcohol120/Read.cs} (51%) create mode 100644 DiscImageChef.DiscImages/Alcohol120/Structs.cs create mode 100644 DiscImageChef.DiscImages/Alcohol120/Write.cs delete mode 100644 DiscImageChef.DiscImages/Anex86.cs create mode 100644 DiscImageChef.DiscImages/Anex86/Anex86.cs create mode 100644 DiscImageChef.DiscImages/Anex86/Identify.cs create mode 100644 DiscImageChef.DiscImages/Anex86/Properties.cs create mode 100644 DiscImageChef.DiscImages/Anex86/Read.cs create mode 100644 DiscImageChef.DiscImages/Anex86/Structs.cs create mode 100644 DiscImageChef.DiscImages/Anex86/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/Anex86/Write.cs delete mode 100644 DiscImageChef.DiscImages/Apple2MG.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Apple2MG.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Constants.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Enums.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Helpers.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Identify.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Properties.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Read.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Structs.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/Apple2MG/Write.cs delete mode 100644 DiscImageChef.DiscImages/AppleDOS.cs create mode 100644 DiscImageChef.DiscImages/AppleDOS/AppleDOS.cs create mode 100644 DiscImageChef.DiscImages/AppleDOS/Constants.cs create mode 100644 DiscImageChef.DiscImages/AppleDOS/Identify.cs create mode 100644 DiscImageChef.DiscImages/AppleDOS/Properties.cs create mode 100644 DiscImageChef.DiscImages/AppleDOS/Read.cs create mode 100644 DiscImageChef.DiscImages/AppleDOS/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/AppleDOS/Write.cs create mode 100644 DiscImageChef.DiscImages/AppleNIB/AppleNIB.cs create mode 100644 DiscImageChef.DiscImages/AppleNIB/Constants.cs create mode 100644 DiscImageChef.DiscImages/AppleNIB/Helpers.cs create mode 100644 DiscImageChef.DiscImages/AppleNIB/Identify.cs create mode 100644 DiscImageChef.DiscImages/AppleNIB/Properties.cs rename DiscImageChef.DiscImages/{AppleNIB.cs => AppleNIB/Read.cs} (60%) create mode 100644 DiscImageChef.DiscImages/AppleNIB/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/Apridisk.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Apridisk.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Compression.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Constants.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Enums.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Helpers.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Identify.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Properties.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Read.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Structs.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/Apridisk/Write.cs delete mode 100644 DiscImageChef.DiscImages/BLU.cs create mode 100644 DiscImageChef.DiscImages/BLU/BLU.cs create mode 100644 DiscImageChef.DiscImages/BLU/Constants.cs create mode 100644 DiscImageChef.DiscImages/BLU/Identify.cs create mode 100644 DiscImageChef.DiscImages/BLU/Properties.cs create mode 100644 DiscImageChef.DiscImages/BLU/Read.cs create mode 100644 DiscImageChef.DiscImages/BLU/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/BLU/Write.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite4/BlindWrite4.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite4/Constants.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite4/Enums.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite4/Identify.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite4/Properties.cs rename DiscImageChef.DiscImages/{BlindWrite4.cs => BlindWrite4/Read.cs} (89%) create mode 100644 DiscImageChef.DiscImages/BlindWrite4/Structs.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite5/BlindWrite5.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite5/Constants.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite5/Enums.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite5/Helpers.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite5/Identify.cs create mode 100644 DiscImageChef.DiscImages/BlindWrite5/Properties.cs rename DiscImageChef.DiscImages/{BlindWrite5.cs => BlindWrite5/Read.cs} (87%) create mode 100644 DiscImageChef.DiscImages/BlindWrite5/Structs.cs create mode 100644 DiscImageChef.DiscImages/CDRDAO/CDRDAO.cs create mode 100644 DiscImageChef.DiscImages/CDRDAO/Constants.cs create mode 100644 DiscImageChef.DiscImages/CDRDAO/Helpers.cs create mode 100644 DiscImageChef.DiscImages/CDRDAO/Identify.cs create mode 100644 DiscImageChef.DiscImages/CDRDAO/Properties.cs rename DiscImageChef.DiscImages/{CDRDAO.cs => CDRDAO/Read.cs} (59%) create mode 100644 DiscImageChef.DiscImages/CDRDAO/Structs.cs create mode 100644 DiscImageChef.DiscImages/CDRDAO/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/CDRDAO/Write.cs create mode 100644 DiscImageChef.DiscImages/CDRWin/CDRWin.cs create mode 100644 DiscImageChef.DiscImages/CDRWin/Constants.cs create mode 100644 DiscImageChef.DiscImages/CDRWin/Helpers.cs create mode 100644 DiscImageChef.DiscImages/CDRWin/Identify.cs create mode 100644 DiscImageChef.DiscImages/CDRWin/Properties.cs rename DiscImageChef.DiscImages/{CDRWin.cs => CDRWin/Read.cs} (62%) create mode 100644 DiscImageChef.DiscImages/CDRWin/Structs.cs create mode 100644 DiscImageChef.DiscImages/CDRWin/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/CDRWin/Write.cs create mode 100644 DiscImageChef.DiscImages/CHD/CHD.cs create mode 100644 DiscImageChef.DiscImages/CHD/Constants.cs create mode 100644 DiscImageChef.DiscImages/CHD/Enums.cs create mode 100644 DiscImageChef.DiscImages/CHD/Helpers.cs create mode 100644 DiscImageChef.DiscImages/CHD/Identify.cs create mode 100644 DiscImageChef.DiscImages/CHD/Properties.cs rename DiscImageChef.DiscImages/{CHD.cs => CHD/Read.cs} (74%) create mode 100644 DiscImageChef.DiscImages/CHD/Structs.cs create mode 100644 DiscImageChef.DiscImages/CPCDSK/CPCDSK.cs create mode 100644 DiscImageChef.DiscImages/CPCDSK/Constants.cs create mode 100644 DiscImageChef.DiscImages/CPCDSK/Helpers.cs create mode 100644 DiscImageChef.DiscImages/CPCDSK/Identify.cs create mode 100644 DiscImageChef.DiscImages/CPCDSK/Properties.cs rename DiscImageChef.DiscImages/{CPCDSK.cs => CPCDSK/Read.cs} (55%) create mode 100644 DiscImageChef.DiscImages/CPCDSK/Structs.cs create mode 100644 DiscImageChef.DiscImages/CPCDSK/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/CisCopy.cs create mode 100644 DiscImageChef.DiscImages/CisCopy/CisCopy.cs create mode 100644 DiscImageChef.DiscImages/CisCopy/Enums.cs create mode 100644 DiscImageChef.DiscImages/CisCopy/Identify.cs create mode 100644 DiscImageChef.DiscImages/CisCopy/Properties.cs create mode 100644 DiscImageChef.DiscImages/CisCopy/Read.cs create mode 100644 DiscImageChef.DiscImages/CisCopy/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/CisCopy/Write.cs create mode 100644 DiscImageChef.DiscImages/CloneCD/CloneCD.cs create mode 100644 DiscImageChef.DiscImages/CloneCD/Constants.cs create mode 100644 DiscImageChef.DiscImages/CloneCD/Helpers.cs create mode 100644 DiscImageChef.DiscImages/CloneCD/Identify.cs create mode 100644 DiscImageChef.DiscImages/CloneCD/Properties.cs rename DiscImageChef.DiscImages/{CloneCD.cs => CloneCD/Read.cs} (67%) create mode 100644 DiscImageChef.DiscImages/CloneCD/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/CloneCD/Write.cs delete mode 100644 DiscImageChef.DiscImages/CopyQM.cs create mode 100644 DiscImageChef.DiscImages/CopyQM/Constants.cs create mode 100644 DiscImageChef.DiscImages/CopyQM/CopyQM.cs create mode 100644 DiscImageChef.DiscImages/CopyQM/Identify.cs create mode 100644 DiscImageChef.DiscImages/CopyQM/Properties.cs create mode 100644 DiscImageChef.DiscImages/CopyQM/Read.cs create mode 100644 DiscImageChef.DiscImages/CopyQM/Structs.cs create mode 100644 DiscImageChef.DiscImages/CopyQM/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/D88/Constants.cs create mode 100644 DiscImageChef.DiscImages/D88/D88.cs create mode 100644 DiscImageChef.DiscImages/D88/Enums.cs create mode 100644 DiscImageChef.DiscImages/D88/Identify.cs create mode 100644 DiscImageChef.DiscImages/D88/Properties.cs rename DiscImageChef.DiscImages/{D88.cs => D88/Read.cs} (54%) create mode 100644 DiscImageChef.DiscImages/D88/Structs.cs create mode 100644 DiscImageChef.DiscImages/D88/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/DART/Constants.cs create mode 100644 DiscImageChef.DiscImages/DART/DART.cs create mode 100644 DiscImageChef.DiscImages/DART/Identify.cs create mode 100644 DiscImageChef.DiscImages/DART/Properties.cs rename DiscImageChef.DiscImages/{DART.cs => DART/Read.cs} (64%) create mode 100644 DiscImageChef.DiscImages/DART/Structs.cs create mode 100644 DiscImageChef.DiscImages/DART/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/DIM/Constants.cs create mode 100644 DiscImageChef.DiscImages/DIM/DIM.cs create mode 100644 DiscImageChef.DiscImages/DIM/Enums.cs create mode 100644 DiscImageChef.DiscImages/DIM/Identify.cs create mode 100644 DiscImageChef.DiscImages/DIM/Properties.cs rename DiscImageChef.DiscImages/{DIM.cs => DIM/Read.cs} (56%) create mode 100644 DiscImageChef.DiscImages/DIM/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/DiscFerret/Constants.cs create mode 100644 DiscImageChef.DiscImages/DiscFerret/DiscFerret.cs create mode 100644 DiscImageChef.DiscImages/DiscFerret/Identify.cs create mode 100644 DiscImageChef.DiscImages/DiscFerret/Properties.cs rename DiscImageChef.DiscImages/{DiscFerret.cs => DiscFerret/Read.cs} (54%) create mode 100644 DiscImageChef.DiscImages/DiscFerret/Structs.cs create mode 100644 DiscImageChef.DiscImages/DiscFerret/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/DiscImageChef.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/ClauniaSubchannelTransform.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Constants.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/DiscImageChef.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Enums.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Helpers.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Identify.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Properties.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Read.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Structs.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Verify.cs create mode 100644 DiscImageChef.DiscImages/DiscImageChef/Write.cs create mode 100644 DiscImageChef.DiscImages/DiscJuggler/DiscJuggler.cs create mode 100644 DiscImageChef.DiscImages/DiscJuggler/Helpers.cs create mode 100644 DiscImageChef.DiscImages/DiscJuggler/Identify.cs create mode 100644 DiscImageChef.DiscImages/DiscJuggler/Properties.cs rename DiscImageChef.DiscImages/{DiscJuggler.cs => DiscJuggler/Read.cs} (93%) delete mode 100644 DiscImageChef.DiscImages/DiskCopy42.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Constants.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/DiskCopy42.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Helpers.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Identify.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Properties.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Read.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Structs.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/DiskCopy42/Write.cs delete mode 100644 DiscImageChef.DiscImages/DriDiskCopy.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Constants.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/DriDiskCopy.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Enums.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Identify.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Properties.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Read.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Structs.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/DriDiskCopy/Write.cs create mode 100644 DiscImageChef.DiscImages/GDI/Constants.cs create mode 100644 DiscImageChef.DiscImages/GDI/GDI.cs create mode 100644 DiscImageChef.DiscImages/GDI/Identify.cs create mode 100644 DiscImageChef.DiscImages/GDI/Properties.cs rename DiscImageChef.DiscImages/{GDI.cs => GDI/Read.cs} (82%) create mode 100644 DiscImageChef.DiscImages/GDI/Structs.cs create mode 100644 DiscImageChef.DiscImages/GDI/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/HDCopy.cs create mode 100644 DiscImageChef.DiscImages/HDCopy/HDCopy.cs create mode 100644 DiscImageChef.DiscImages/HDCopy/Helpers.cs create mode 100644 DiscImageChef.DiscImages/HDCopy/Identify.cs create mode 100644 DiscImageChef.DiscImages/HDCopy/Properties.cs create mode 100644 DiscImageChef.DiscImages/HDCopy/Read.cs create mode 100644 DiscImageChef.DiscImages/HDCopy/Structs.cs create mode 100644 DiscImageChef.DiscImages/HDCopy/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/IMD/Constants.cs create mode 100644 DiscImageChef.DiscImages/IMD/Enums.cs create mode 100644 DiscImageChef.DiscImages/IMD/IMD.cs create mode 100644 DiscImageChef.DiscImages/IMD/Identify.cs create mode 100644 DiscImageChef.DiscImages/IMD/Properties.cs rename DiscImageChef.DiscImages/{IMD.cs => IMD/Read.cs} (53%) create mode 100644 DiscImageChef.DiscImages/IMD/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/KryoFlux/Constants.cs create mode 100644 DiscImageChef.DiscImages/KryoFlux/Enums.cs create mode 100644 DiscImageChef.DiscImages/KryoFlux/Identify.cs create mode 100644 DiscImageChef.DiscImages/KryoFlux/KryoFlux.cs create mode 100644 DiscImageChef.DiscImages/KryoFlux/Properties.cs rename DiscImageChef.DiscImages/{KryoFlux.cs => KryoFlux/Read.cs} (63%) create mode 100644 DiscImageChef.DiscImages/KryoFlux/Structs.cs create mode 100644 DiscImageChef.DiscImages/KryoFlux/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/MaxiDisk.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/Enums.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/Identify.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/MaxiDisk.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/Properties.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/Read.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/Structs.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/MaxiDisk/Write.cs create mode 100644 DiscImageChef.DiscImages/NDIF/Constants.cs create mode 100644 DiscImageChef.DiscImages/NDIF/Identify.cs create mode 100644 DiscImageChef.DiscImages/NDIF/NDIF.cs create mode 100644 DiscImageChef.DiscImages/NDIF/Properties.cs rename DiscImageChef.DiscImages/{NDIF.cs => NDIF/Read.cs} (61%) create mode 100644 DiscImageChef.DiscImages/NDIF/Structs.cs create mode 100644 DiscImageChef.DiscImages/NDIF/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/NHDr0.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/Constants.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/Identify.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/NHDr0.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/Properties.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/Read.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/Structs.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/NHDr0/Write.cs create mode 100644 DiscImageChef.DiscImages/Nero/Constants.cs create mode 100644 DiscImageChef.DiscImages/Nero/Enums.cs create mode 100644 DiscImageChef.DiscImages/Nero/Helpers.cs create mode 100644 DiscImageChef.DiscImages/Nero/Identify.cs create mode 100644 DiscImageChef.DiscImages/Nero/Nero.cs create mode 100644 DiscImageChef.DiscImages/Nero/Properties.cs rename DiscImageChef.DiscImages/{Nero.cs => Nero/Read.cs} (73%) create mode 100644 DiscImageChef.DiscImages/Nero/Structs.cs delete mode 100644 DiscImageChef.DiscImages/Parallels.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Constants.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Identify.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Parallels.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Properties.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Read.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Structs.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/Parallels/Write.cs delete mode 100644 DiscImageChef.DiscImages/PartClone.cs create mode 100644 DiscImageChef.DiscImages/PartClone/Constants.cs create mode 100644 DiscImageChef.DiscImages/PartClone/Helpers.cs create mode 100644 DiscImageChef.DiscImages/PartClone/Identify.cs create mode 100644 DiscImageChef.DiscImages/PartClone/PartClone.cs create mode 100644 DiscImageChef.DiscImages/PartClone/Properties.cs create mode 100644 DiscImageChef.DiscImages/PartClone/Read.cs create mode 100644 DiscImageChef.DiscImages/PartClone/Structs.cs create mode 100644 DiscImageChef.DiscImages/PartClone/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/Partimage/Constants.cs create mode 100644 DiscImageChef.DiscImages/Partimage/Enums.cs create mode 100644 DiscImageChef.DiscImages/Partimage/Helpers.cs create mode 100644 DiscImageChef.DiscImages/Partimage/Identify.cs create mode 100644 DiscImageChef.DiscImages/Partimage/Partimage.cs create mode 100644 DiscImageChef.DiscImages/Partimage/Properties.cs rename DiscImageChef.DiscImages/{Partimage.cs => Partimage/Read.cs} (55%) create mode 100644 DiscImageChef.DiscImages/Partimage/Structs.cs create mode 100644 DiscImageChef.DiscImages/Partimage/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/QCOW.cs create mode 100644 DiscImageChef.DiscImages/QCOW/Constants.cs create mode 100644 DiscImageChef.DiscImages/QCOW/Identify.cs create mode 100644 DiscImageChef.DiscImages/QCOW/Properties.cs create mode 100644 DiscImageChef.DiscImages/QCOW/QCOW.cs create mode 100644 DiscImageChef.DiscImages/QCOW/Read.cs create mode 100644 DiscImageChef.DiscImages/QCOW/Structs.cs create mode 100644 DiscImageChef.DiscImages/QCOW/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/QCOW/Write.cs delete mode 100644 DiscImageChef.DiscImages/QCOW2.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/Constants.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/Identify.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/Properties.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/QCOW2.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/Read.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/Structs.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/QCOW2/Write.cs create mode 100644 DiscImageChef.DiscImages/QED/Constants.cs create mode 100644 DiscImageChef.DiscImages/QED/Enums.cs create mode 100644 DiscImageChef.DiscImages/QED/Helpers.cs create mode 100644 DiscImageChef.DiscImages/QED/Identify.cs create mode 100644 DiscImageChef.DiscImages/QED/Properties.cs rename DiscImageChef.DiscImages/{ => QED}/QED.cs (62%) create mode 100644 DiscImageChef.DiscImages/QED/Read.cs create mode 100644 DiscImageChef.DiscImages/QED/Structs.cs create mode 100644 DiscImageChef.DiscImages/QED/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/QED/Write.cs delete mode 100644 DiscImageChef.DiscImages/RayDIM.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Constants.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Enums.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Identify.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Properties.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/RayDIM.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Read.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Structs.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/RayDIM/Write.cs delete mode 100644 DiscImageChef.DiscImages/RsIde.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Constants.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Enums.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Helpers.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Identify.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Properties.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Read.cs create mode 100644 DiscImageChef.DiscImages/RsIde/RsIde.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Structs.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/RsIde/Write.cs delete mode 100644 DiscImageChef.DiscImages/SaveDskF.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/Constants.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/Identify.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/Properties.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/Read.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/SaveDskF.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/Structs.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/SaveDskF/Write.cs create mode 100644 DiscImageChef.DiscImages/SuperCardPro/Constants.cs create mode 100644 DiscImageChef.DiscImages/SuperCardPro/Enums.cs create mode 100644 DiscImageChef.DiscImages/SuperCardPro/Helpers.cs create mode 100644 DiscImageChef.DiscImages/SuperCardPro/Identify.cs create mode 100644 DiscImageChef.DiscImages/SuperCardPro/Properties.cs rename DiscImageChef.DiscImages/{SuperCardPro.cs => SuperCardPro/Read.cs} (61%) create mode 100644 DiscImageChef.DiscImages/SuperCardPro/Structs.cs create mode 100644 DiscImageChef.DiscImages/SuperCardPro/SuperCardPro.cs create mode 100644 DiscImageChef.DiscImages/SuperCardPro/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/T98.cs create mode 100644 DiscImageChef.DiscImages/T98/Identify.cs create mode 100644 DiscImageChef.DiscImages/T98/Properties.cs create mode 100644 DiscImageChef.DiscImages/T98/Read.cs create mode 100644 DiscImageChef.DiscImages/T98/T98.cs create mode 100644 DiscImageChef.DiscImages/T98/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/T98/Write.cs create mode 100644 DiscImageChef.DiscImages/TeleDisk/Constants.cs create mode 100644 DiscImageChef.DiscImages/TeleDisk/Helpers.cs create mode 100644 DiscImageChef.DiscImages/TeleDisk/Identify.cs create mode 100644 DiscImageChef.DiscImages/TeleDisk/Properties.cs rename DiscImageChef.DiscImages/{TeleDisk.cs => TeleDisk/Read.cs} (52%) create mode 100644 DiscImageChef.DiscImages/TeleDisk/Structs.cs create mode 100644 DiscImageChef.DiscImages/TeleDisk/TeleDisk.cs create mode 100644 DiscImageChef.DiscImages/TeleDisk/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/UDIF.cs create mode 100644 DiscImageChef.DiscImages/UDIF/Constants.cs create mode 100644 DiscImageChef.DiscImages/UDIF/Identify.cs create mode 100644 DiscImageChef.DiscImages/UDIF/Properties.cs create mode 100644 DiscImageChef.DiscImages/UDIF/Read.cs create mode 100644 DiscImageChef.DiscImages/UDIF/Structs.cs create mode 100644 DiscImageChef.DiscImages/UDIF/UDIF.cs create mode 100644 DiscImageChef.DiscImages/UDIF/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/UDIF/Write.cs create mode 100644 DiscImageChef.DiscImages/UkvFdi/Constants.cs create mode 100644 DiscImageChef.DiscImages/UkvFdi/Enums.cs create mode 100644 DiscImageChef.DiscImages/UkvFdi/Helpers.cs create mode 100644 DiscImageChef.DiscImages/UkvFdi/Identify.cs create mode 100644 DiscImageChef.DiscImages/UkvFdi/Properties.cs rename DiscImageChef.DiscImages/{UkvFdi.cs => UkvFdi/Read.cs} (55%) create mode 100644 DiscImageChef.DiscImages/UkvFdi/Structs.cs create mode 100644 DiscImageChef.DiscImages/UkvFdi/UkvFdi.cs create mode 100644 DiscImageChef.DiscImages/UkvFdi/Unsupported.cs delete mode 100644 DiscImageChef.DiscImages/VDI.cs create mode 100644 DiscImageChef.DiscImages/VDI/Constants.cs create mode 100644 DiscImageChef.DiscImages/VDI/Enums.cs create mode 100644 DiscImageChef.DiscImages/VDI/Identify.cs create mode 100644 DiscImageChef.DiscImages/VDI/Properties.cs create mode 100644 DiscImageChef.DiscImages/VDI/Read.cs create mode 100644 DiscImageChef.DiscImages/VDI/Structs.cs create mode 100644 DiscImageChef.DiscImages/VDI/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/VDI/VDI.cs create mode 100644 DiscImageChef.DiscImages/VDI/Write.cs create mode 100644 DiscImageChef.DiscImages/VHD/Constants.cs create mode 100644 DiscImageChef.DiscImages/VHD/Helpers.cs create mode 100644 DiscImageChef.DiscImages/VHD/Identify.cs create mode 100644 DiscImageChef.DiscImages/VHD/Properties.cs rename DiscImageChef.DiscImages/{VHD.cs => VHD/Read.cs} (58%) create mode 100644 DiscImageChef.DiscImages/VHD/Structs.cs create mode 100644 DiscImageChef.DiscImages/VHD/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/VHD/VHD.cs create mode 100644 DiscImageChef.DiscImages/VHD/Write.cs create mode 100644 DiscImageChef.DiscImages/VHDX/Constants.cs create mode 100644 DiscImageChef.DiscImages/VHDX/Helpers.cs create mode 100644 DiscImageChef.DiscImages/VHDX/Identify.cs create mode 100644 DiscImageChef.DiscImages/VHDX/Properties.cs rename DiscImageChef.DiscImages/{VHDX.cs => VHDX/Read.cs} (57%) create mode 100644 DiscImageChef.DiscImages/VHDX/Structs.cs create mode 100644 DiscImageChef.DiscImages/VHDX/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/VHDX/VHDX.cs create mode 100644 DiscImageChef.DiscImages/VMware/Constants.cs create mode 100644 DiscImageChef.DiscImages/VMware/Identify.cs create mode 100644 DiscImageChef.DiscImages/VMware/Properties.cs rename DiscImageChef.DiscImages/{VMware.cs => VMware/Read.cs} (52%) create mode 100644 DiscImageChef.DiscImages/VMware/Structs.cs create mode 100644 DiscImageChef.DiscImages/VMware/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/VMware/VMware.cs create mode 100644 DiscImageChef.DiscImages/VMware/Write.cs delete mode 100644 DiscImageChef.DiscImages/Virtual98.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Constants.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Identify.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Properties.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Read.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Structs.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Unsupported.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Virtual98.cs create mode 100644 DiscImageChef.DiscImages/Virtual98/Write.cs create mode 100644 DiscImageChef.DiscImages/ZZZRawImage/Constants.cs create mode 100644 DiscImageChef.DiscImages/ZZZRawImage/Helpers.cs create mode 100644 DiscImageChef.DiscImages/ZZZRawImage/Identify.cs create mode 100644 DiscImageChef.DiscImages/ZZZRawImage/Properties.cs rename DiscImageChef.DiscImages/{ZZZRawImage.cs => ZZZRawImage/Read.cs} (72%) create mode 100644 DiscImageChef.DiscImages/ZZZRawImage/Write.cs create mode 100644 DiscImageChef.DiscImages/ZZZRawImage/ZZZRawImage.cs diff --git a/.idea/.idea.DiscImageChef/.idea/contentModel.xml b/.idea/.idea.DiscImageChef/.idea/contentModel.xml index c7497797d..b8f015afe 100644 --- a/.idea/.idea.DiscImageChef/.idea/contentModel.xml +++ b/.idea/.idea.DiscImageChef/.idea/contentModel.xml @@ -485,60 +485,531 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DiscImageChef.DiscImages/Alcohol120/Alcohol120.cs b/DiscImageChef.DiscImages/Alcohol120/Alcohol120.cs new file mode 100644 index 000000000..893b8a705 --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Alcohol120.cs @@ -0,0 +1,88 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Alcohol120.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 : IWritableImage + { + AlcoholFooter alcFooter; + IFilter alcImage; + Dictionary alcSessions; + Dictionary> alcToc; + Dictionary alcTrackExtras; + Dictionary alcTracks; + byte[] bca; + FileStream descriptorStream; + byte[] dmi; + byte[] fullToc; + ImageInfo imageInfo; + Stream imageStream; + bool isDvd; + Dictionary offsetmap; + byte[] pfi; + Dictionary trackFlags; + List writingTracks; + + public Alcohol120() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = true, + HasSessions = true, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120/Constants.cs b/DiscImageChef.DiscImages/Alcohol120/Constants.cs new file mode 100644 index 000000000..d4b69b140 --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Constants.cs @@ -0,0 +1,40 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 + { + readonly byte[] alcoholSignature = + {0x4d, 0x45, 0x44, 0x49, 0x41, 0x20, 0x44, 0x45, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x4f, 0x52}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120/Enums.cs b/DiscImageChef.DiscImages/Alcohol120/Enums.cs new file mode 100644 index 000000000..80d218a5c --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Enums.cs @@ -0,0 +1,69 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics.CodeAnalysis; + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 + { + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum AlcoholMediumType : ushort + { + CD = 0x00, + CDR = 0x01, + CDRW = 0x02, + DVD = 0x10, + DVDR = 0x12 + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum AlcoholTrackMode : byte + { + NoData = 0x00, + DVD = 0x02, + Audio = 0xA9, + Mode1 = 0xAA, + Mode2 = 0xAB, + Mode2F1 = 0xEC, + Mode2F2 = 0xED, + Mode2F1Alt = 0xAC, + Mode2F2Alt = 0xAD + } + + enum AlcoholSubchannelMode : byte + { + None = 0x00, + Interleaved = 0x08 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120/Helpers.cs b/DiscImageChef.DiscImages/Alcohol120/Helpers.cs new file mode 100644 index 000000000..30841d8f2 --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Helpers.cs @@ -0,0 +1,157 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 + { + static ushort AlcoholTrackModeToBytesPerSector(AlcoholTrackMode trackMode) + { + switch(trackMode) + { + case AlcoholTrackMode.Audio: + case AlcoholTrackMode.Mode1: + case AlcoholTrackMode.Mode2: + case AlcoholTrackMode.Mode2F1: + case AlcoholTrackMode.Mode2F2: + case AlcoholTrackMode.Mode2F2Alt: + case AlcoholTrackMode.Mode2F1Alt: return 2352; + case AlcoholTrackMode.DVD: return 2048; + default: return 0; + } + } + + static ushort AlcoholTrackModeToCookedBytesPerSector(AlcoholTrackMode trackMode) + { + switch(trackMode) + { + case AlcoholTrackMode.Mode1: + case AlcoholTrackMode.Mode2F1: + case AlcoholTrackMode.Mode2F1Alt: return 2048; + case AlcoholTrackMode.Mode2F2: + case AlcoholTrackMode.Mode2F2Alt: return 2324; + case AlcoholTrackMode.Mode2: return 2336; + case AlcoholTrackMode.Audio: return 2352; + case AlcoholTrackMode.DVD: return 2048; + default: return 0; + } + } + + static TrackType AlcoholTrackTypeToTrackType(AlcoholTrackMode trackType) + { + switch(trackType) + { + case AlcoholTrackMode.Mode1: return TrackType.CdMode1; + case AlcoholTrackMode.Mode2F1: + case AlcoholTrackMode.Mode2F1Alt: return TrackType.CdMode2Form1; + case AlcoholTrackMode.Mode2F2: + case AlcoholTrackMode.Mode2F2Alt: return TrackType.CdMode2Form2; + case AlcoholTrackMode.Mode2: return TrackType.CdMode2Formless; + case AlcoholTrackMode.Audio: return TrackType.Audio; + default: return TrackType.Data; + } + } + + static MediaType AlcoholMediumTypeToMediaType(AlcoholMediumType discType) + { + switch(discType) + { + case AlcoholMediumType.CD: return MediaType.CD; + case AlcoholMediumType.CDR: return MediaType.CDR; + case AlcoholMediumType.CDRW: return MediaType.CDRW; + case AlcoholMediumType.DVD: return MediaType.DVDROM; + case AlcoholMediumType.DVDR: return MediaType.DVDR; + default: return MediaType.Unknown; + } + } + + static AlcoholMediumType MediaTypeToAlcohol(MediaType type) + { + switch(type) + { + case MediaType.CD: + case MediaType.CDDA: + case MediaType.CDEG: + case MediaType.CDG: + case MediaType.CDI: + case MediaType.CDMIDI: + case MediaType.CDPLUS: + case MediaType.CDROM: + case MediaType.CDROMXA: + case MediaType.CDV: + case MediaType.DTSCD: + case MediaType.JaguarCD: + case MediaType.MEGACD: + case MediaType.PS1CD: + case MediaType.PS2CD: + case MediaType.SuperCDROM2: + case MediaType.SVCD: + case MediaType.SATURNCD: + case MediaType.ThreeDO: + case MediaType.VCD: + case MediaType.VCDHD: return AlcoholMediumType.CD; + case MediaType.CDR: return AlcoholMediumType.CDR; + case MediaType.CDRW: + case MediaType.CDMRW: return AlcoholMediumType.CDRW; + case MediaType.DVDR: + case MediaType.DVDRW: + case MediaType.DVDPR: + case MediaType.DVDRDL: + case MediaType.DVDRWDL: + case MediaType.DVDPRDL: + case MediaType.DVDPRWDL: return AlcoholMediumType.DVDR; + default: return AlcoholMediumType.DVD; + } + } + + static AlcoholTrackMode TrackTypeToAlcohol(TrackType type) + { + switch(type) + { + case TrackType.Audio: return AlcoholTrackMode.Audio; + case TrackType.CdMode1: return AlcoholTrackMode.Mode1; + case TrackType.CdMode2Formless: return AlcoholTrackMode.Mode2; + case TrackType.CdMode2Form1: return AlcoholTrackMode.Mode2F1; + case TrackType.CdMode2Form2: return AlcoholTrackMode.Mode2F2; + default: return AlcoholTrackMode.DVD; + } + } + + static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) + { + return ((byte)((sector + 150) / 75 / 60), (byte)((sector + 150) / 75 % 60), (byte)((sector + 150) % 75)); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120/Identify.cs b/DiscImageChef.DiscImages/Alcohol120/Identify.cs new file mode 100644 index 000000000..5d89d3420 --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Identify.cs @@ -0,0 +1,59 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < 88) return false; + + byte[] hdr = new byte[88]; + stream.Read(hdr, 0, 88); + IntPtr hdrPtr = Marshal.AllocHGlobal(88); + Marshal.Copy(hdr, 0, hdrPtr, 88); + AlcoholHeader header = (AlcoholHeader)Marshal.PtrToStructure(hdrPtr, typeof(AlcoholHeader)); + Marshal.FreeHGlobal(hdrPtr); + + return header.signature.SequenceEqual(alcoholSignature); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120/Properties.cs b/DiscImageChef.DiscImages/Alcohol120/Properties.cs new file mode 100644 index 000000000..d50c7542d --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Properties.cs @@ -0,0 +1,141 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 + { + public ImageInfo Info => imageInfo; + public string Name => "Alcohol 120% Media Descriptor Structure"; + public Guid Id => new Guid("A78FBEBA-0307-4915-BDE3-B8A3B57F843F"); + + public string Format => "Alcohol 120% Media Descriptor Structure"; + + public List Partitions { get; private set; } + + public List Tracks + { + get + { + List tracks = new List(); + + foreach(AlcoholTrack alcTrack in alcTracks.Values) + { + ushort sessionNo = + (from session in Sessions + where alcTrack.point >= session.StartTrack || alcTrack.point <= session.EndTrack + select session.SessionSequence).FirstOrDefault(); + + if(!alcTrackExtras.TryGetValue(alcTrack.point, out AlcoholTrackExtra alcExtra)) continue; + + Track dicTrack = new Track + { + Indexes = new Dictionary {{1, alcTrack.startLba}}, + TrackStartSector = alcTrack.startLba, + TrackEndSector = alcTrack.startLba + alcExtra.sectors - 1, + TrackPregap = alcExtra.pregap, + TrackSession = sessionNo, + TrackSequence = alcTrack.point, + TrackType = AlcoholTrackTypeToTrackType(alcTrack.mode), + TrackFilter = alcImage, + TrackFile = alcImage.GetFilename(), + TrackFileOffset = alcTrack.startOffset, + TrackFileType = "BINARY", + TrackRawBytesPerSector = alcTrack.sectorSize, + TrackBytesPerSector = AlcoholTrackModeToCookedBytesPerSector(alcTrack.mode) + }; + + switch(alcTrack.subMode) + { + case AlcoholSubchannelMode.Interleaved: + dicTrack.TrackSubchannelFilter = alcImage; + dicTrack.TrackSubchannelFile = alcImage.GetFilename(); + dicTrack.TrackSubchannelOffset = alcTrack.startOffset; + dicTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + break; + case AlcoholSubchannelMode.None: + dicTrack.TrackSubchannelType = TrackSubchannelType.None; + break; + } + + tracks.Add(dicTrack); + } + + return tracks; + } + } + + public List Sessions { get; private set; } + + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + + public IEnumerable SupportedMediaTags => + new[] {MediaTagType.CD_FullTOC, MediaTagType.DVD_BCA, MediaTagType.DVD_DMI, MediaTagType.DVD_PFI}; + public IEnumerable SupportedSectorTags => + new[] + { + SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, + SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubHeader, + SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, SectorTagType.CdSectorSubchannel + }; + public IEnumerable SupportedMediaTypes => + new[] + { + MediaType.BDR, MediaType.BDRE, MediaType.BDREXL, MediaType.BDROM, MediaType.BDRXL, MediaType.CBHD, + MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, + MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, + MediaType.CDV, MediaType.DVDDownload, MediaType.DVDPR, MediaType.DVDPRDL, MediaType.DVDPRW, + MediaType.DVDPRWDL, MediaType.DVDR, MediaType.DVDRAM, MediaType.DVDRDL, MediaType.DVDROM, + MediaType.DVDRW, MediaType.DVDRWDL, MediaType.EVD, MediaType.FDDVD, MediaType.DTSCD, MediaType.FVD, + MediaType.HDDVDR, MediaType.HDDVDRAM, MediaType.HDDVDRDL, MediaType.HDDVDROM, MediaType.HDDVDRW, + MediaType.HDDVDRWDL, MediaType.HDVMD, MediaType.HVD, MediaType.JaguarCD, MediaType.MEGACD, + MediaType.PD650, MediaType.PD650_WORM, MediaType.PS1CD, MediaType.PS2CD, MediaType.PS2DVD, + MediaType.PS3BD, MediaType.PS3DVD, MediaType.PS4BD, MediaType.SuperCDROM2, MediaType.SVCD, + MediaType.SVOD, MediaType.SATURNCD, MediaType.ThreeDO, MediaType.UDO, MediaType.UDO2, + MediaType.UDO2_WORM, MediaType.UMD, MediaType.VCD, MediaType.VCDHD, MediaType.NeoGeoCD, MediaType.PCFX + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable KnownExtensions => new[] {".mds"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120.cs b/DiscImageChef.DiscImages/Alcohol120/Read.cs similarity index 51% rename from DiscImageChef.DiscImages/Alcohol120.cs rename to DiscImageChef.DiscImages/Alcohol120/Read.cs index 76451c661..d9d3dc5df 100644 --- a/DiscImageChef.DiscImages/Alcohol120.cs +++ b/DiscImageChef.DiscImages/Alcohol120/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : Alcohol120.cs +// Filename : Read.cs // Author(s) : Natalia Portillo // // Component : Disc image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Alcohol 120% disc images. +// Reads Alcohol 120% disc images. // // --[ License ] -------------------------------------------------------------- // @@ -32,7 +32,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -46,141 +45,13 @@ using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Decoders.CD; using DiscImageChef.Decoders.DVD; -using DiscImageChef.Filters; -using Schemas; using DMI = DiscImageChef.Decoders.Xbox.DMI; using Session = DiscImageChef.CommonTypes.Structs.Session; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - public class Alcohol120 : IWritableImage + public partial class Alcohol120 { - readonly byte[] alcoholSignature = - {0x4d, 0x45, 0x44, 0x49, 0x41, 0x20, 0x44, 0x45, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x4f, 0x52}; - AlcoholFooter alcFooter; - IFilter alcImage; - Dictionary alcSessions; - Dictionary> alcToc; - Dictionary alcTrackExtras; - Dictionary alcTracks; - byte[] bca; - FileStream descriptorStream; - byte[] dmi; - byte[] fullToc; - ImageInfo imageInfo; - Stream imageStream; - bool isDvd; - Dictionary offsetmap; - byte[] pfi; - Dictionary trackFlags; - List writingTracks; - - public Alcohol120() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = true, - HasSessions = true, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - public string Name => "Alcohol 120% Media Descriptor Structure"; - public Guid Id => new Guid("A78FBEBA-0307-4915-BDE3-B8A3B57F843F"); - - public string Format => "Alcohol 120% Media Descriptor Structure"; - - public List Partitions { get; private set; } - - public List Tracks - { - get - { - List tracks = new List(); - - foreach(AlcoholTrack alcTrack in alcTracks.Values) - { - ushort sessionNo = - (from session in Sessions - where alcTrack.point >= session.StartTrack || alcTrack.point <= session.EndTrack - select session.SessionSequence).FirstOrDefault(); - - if(!alcTrackExtras.TryGetValue(alcTrack.point, out AlcoholTrackExtra alcExtra)) continue; - - Track dicTrack = new Track - { - Indexes = new Dictionary {{1, alcTrack.startLba}}, - TrackStartSector = alcTrack.startLba, - TrackEndSector = alcTrack.startLba + alcExtra.sectors - 1, - TrackPregap = alcExtra.pregap, - TrackSession = sessionNo, - TrackSequence = alcTrack.point, - TrackType = AlcoholTrackTypeToTrackType(alcTrack.mode), - TrackFilter = alcImage, - TrackFile = alcImage.GetFilename(), - TrackFileOffset = alcTrack.startOffset, - TrackFileType = "BINARY", - TrackRawBytesPerSector = alcTrack.sectorSize, - TrackBytesPerSector = AlcoholTrackModeToCookedBytesPerSector(alcTrack.mode) - }; - - switch(alcTrack.subMode) - { - case AlcoholSubchannelMode.Interleaved: - dicTrack.TrackSubchannelFilter = alcImage; - dicTrack.TrackSubchannelFile = alcImage.GetFilename(); - dicTrack.TrackSubchannelOffset = alcTrack.startOffset; - dicTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; - break; - case AlcoholSubchannelMode.None: - dicTrack.TrackSubchannelType = TrackSubchannelType.None; - break; - } - - tracks.Add(dicTrack); - } - - return tracks; - } - } - - public List Sessions { get; private set; } - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < 88) return false; - - byte[] hdr = new byte[88]; - stream.Read(hdr, 0, 88); - IntPtr hdrPtr = Marshal.AllocHGlobal(88); - Marshal.Copy(hdr, 0, hdrPtr, 88); - AlcoholHeader header = (AlcoholHeader)Marshal.PtrToStructure(hdrPtr, typeof(AlcoholHeader)); - Marshal.FreeHGlobal(hdrPtr); - - return header.signature.SequenceEqual(alcoholSignature); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -1454,1202 +1325,5 @@ namespace DiscImageChef.DiscImages { return null; } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable SupportedMediaTags => - new[] {MediaTagType.CD_FullTOC, MediaTagType.DVD_BCA, MediaTagType.DVD_DMI, MediaTagType.DVD_PFI}; - public IEnumerable SupportedSectorTags => - new[] - { - SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, - SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubHeader, - SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, SectorTagType.CdSectorSubchannel - }; - public IEnumerable SupportedMediaTypes => - new[] - { - MediaType.BDR, MediaType.BDRE, MediaType.BDREXL, MediaType.BDROM, MediaType.BDRXL, MediaType.CBHD, - MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, - MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, - MediaType.CDV, MediaType.DVDDownload, MediaType.DVDPR, MediaType.DVDPRDL, MediaType.DVDPRW, - MediaType.DVDPRWDL, MediaType.DVDR, MediaType.DVDRAM, MediaType.DVDRDL, MediaType.DVDROM, - MediaType.DVDRW, MediaType.DVDRWDL, MediaType.EVD, MediaType.FDDVD, MediaType.DTSCD, MediaType.FVD, - MediaType.HDDVDR, MediaType.HDDVDRAM, MediaType.HDDVDRDL, MediaType.HDDVDROM, MediaType.HDDVDRW, - MediaType.HDDVDRWDL, MediaType.HDVMD, MediaType.HVD, MediaType.JaguarCD, MediaType.MEGACD, - MediaType.PD650, MediaType.PD650_WORM, MediaType.PS1CD, MediaType.PS2CD, MediaType.PS2DVD, - MediaType.PS3BD, MediaType.PS3DVD, MediaType.PS4BD, MediaType.SuperCDROM2, MediaType.SVCD, - MediaType.SVOD, MediaType.SATURNCD, MediaType.ThreeDO, MediaType.UDO, MediaType.UDO2, - MediaType.UDO2_WORM, MediaType.UMD, MediaType.VCD, MediaType.VCDHD, MediaType.NeoGeoCD, MediaType.PCFX - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable KnownExtensions => new[] {".mds"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, - uint sectorSize) - { - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try - { - descriptorStream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None); - imageStream = - new - FileStream(Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)) + ".mdf", - FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); - } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - imageInfo.MediaType = mediaType; - - switch(mediaType) - { - case MediaType.CD: - case MediaType.CDDA: - case MediaType.CDEG: - case MediaType.CDG: - case MediaType.CDI: - case MediaType.CDMIDI: - case MediaType.CDMRW: - case MediaType.CDPLUS: - case MediaType.CDR: - case MediaType.CDROM: - case MediaType.CDROMXA: - case MediaType.CDRW: - case MediaType.CDV: - case MediaType.DTSCD: - case MediaType.JaguarCD: - case MediaType.MEGACD: - case MediaType.PS1CD: - case MediaType.PS2CD: - case MediaType.SuperCDROM2: - case MediaType.SVCD: - case MediaType.SATURNCD: - case MediaType.ThreeDO: - case MediaType.VCD: - case MediaType.VCDHD: - isDvd = false; - break; - default: - isDvd = true; - break; - } - - trackFlags = new Dictionary(); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - switch(tag) - { - case MediaTagType.CD_FullTOC: - if(isDvd) - { - ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; - return false; - } - - byte[] fullTocSize = BigEndianBitConverter.GetBytes((short)data.Length); - fullToc = new byte[data.Length + 2]; - Array.Copy(data, 0, fullToc, 2, data.Length); - fullToc[0] = fullTocSize[0]; - fullToc[1] = fullTocSize[1]; - return true; - case MediaTagType.DVD_PFI: - if(!isDvd) - { - ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; - return false; - } - - pfi = data; - return true; - case MediaTagType.DVD_DMI: - if(!isDvd) - { - ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; - return false; - } - - dmi = data; - return true; - case MediaTagType.DVD_BCA: - if(!isDvd) - { - ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; - return false; - } - - bca = data; - return true; - default: - ErrorMessage = $"Unsupported media tag {tag}"; - return false; - } - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) - { - ErrorMessage = "Invalid write mode for this sector"; - return false; - } - - if(data.Length != track.TrackRawBytesPerSector) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - imageStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - imageStream.Write(data, 0, data.Length); - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) - { - ErrorMessage = "Invalid write mode for this sector"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - if(data.Length % track.TrackRawBytesPerSector != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - switch(track.TrackSubchannelType) - { - case TrackSubchannelType.None: - imageStream - .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - imageStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - case TrackSubchannelType.Raw: - case TrackSubchannelType.RawInterleaved: - imageStream - .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)), - SeekOrigin.Begin); - for(uint i = 0; i < length; i++) - { - imageStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); - imageStream.Position += 96; - } - - ErrorMessage = ""; - return true; - default: - ErrorMessage = "Invalid subchannel mode for this sector"; - return false; - } - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(data.Length != track.TrackRawBytesPerSector) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); - - imageStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), - SeekOrigin.Begin); - imageStream.Write(data, 0, data.Length); - - return true; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - if(data.Length % track.TrackRawBytesPerSector != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); - - for(uint i = 0; i < length; i++) - { - imageStream.Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), - SeekOrigin.Begin); - imageStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); - } - - return true; - } - - public bool SetTracks(List tracks) - { - ulong currentDataOffset = 0; - - writingTracks = new List(); - foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) - { - Track newTrack = track; - uint subchannelSize; - switch(track.TrackSubchannelType) - { - case TrackSubchannelType.None: - subchannelSize = 0; - break; - case TrackSubchannelType.Raw: - case TrackSubchannelType.RawInterleaved: - subchannelSize = 96; - break; - default: - ErrorMessage = $"Unsupported subchannel type {track.TrackSubchannelType}"; - return false; - } - - newTrack.TrackFileOffset = currentDataOffset; - - currentDataOffset += (ulong)(newTrack.TrackRawBytesPerSector + subchannelSize) * - (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); - - writingTracks.Add(newTrack); - } - - return true; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - byte sessions = byte.MinValue; - - foreach(Track t in writingTracks) - if(t.TrackSession > byte.MinValue) - sessions = (byte)t.TrackSession; - - AlcoholHeader header = new AlcoholHeader - { - signature = alcoholSignature, - version = new byte[] {1, 5}, - type = MediaTypeToAlcohol(imageInfo.MediaType), - sessions = sessions, - structuresOffset = (uint)(pfi == null ? 0 : 96), - sessionOffset = (uint)(pfi == null ? 96 : 4196), - unknown1 = new ushort[2], - unknown2 = new uint[2], - unknown3 = new uint[6], - unknown4 = new uint[3] - }; - // Alcohol sets this always, Daemon Tool expects this - header.unknown1[0] = 2; - - alcSessions = new Dictionary(); - alcTracks = new Dictionary(); - alcToc = new Dictionary>(); - writingTracks = writingTracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).ToList(); - alcTrackExtras = new Dictionary(); - long currentTrackOffset = header.sessionOffset + Marshal.SizeOf(typeof(AlcoholSession)) * sessions; - - FullTOC.CDFullTOC? decodedToc = FullTOC.Decode(fullToc); - - long currentExtraOffset = currentTrackOffset; - for(int i = 1; i <= sessions; i++) - if(decodedToc.HasValue) - currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * - decodedToc.Value.TrackDescriptors.Count(t => t.SessionNumber == i); - else - { - currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 3; - currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * - writingTracks.Count(t => t.TrackSession == i); - if(i < sessions) currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 2; - } - - long footerOffset = currentExtraOffset + Marshal.SizeOf(typeof(AlcoholTrackExtra)) * writingTracks.Count; - if(bca != null) - { - header.bcaOffset = (uint)footerOffset; - footerOffset += bca.Length; - } - - if(isDvd) - { - alcSessions.Add(1, - new AlcoholSession - { - sessionEnd = - (int)(writingTracks[0].TrackEndSector - writingTracks[0].TrackStartSector + 1), - sessionSequence = 1, - allBlocks = 1, - nonTrackBlocks = 3, - firstTrack = 1, - lastTrack = 1, - trackOffset = 4220 - }); - - footerOffset = 4300; - if(bca != null) footerOffset += bca.Length; - - alcTracks.Add(1, - new AlcoholTrack - { - mode = AlcoholTrackMode.DVD, - adrCtl = 20, - point = 1, - extraOffset = - (uint)(writingTracks[0].TrackEndSector - writingTracks[0].TrackStartSector + 1), - sectorSize = 2048, - files = 1, - footerOffset = (uint)footerOffset, - unknown = new byte[18], - unknown2 = new byte[24] - }); - - alcToc.Add(1, alcTracks); - } - else - for(int i = 1; i <= sessions; i++) - { - Track firstTrack = writingTracks.First(t => t.TrackSession == i); - Track lastTrack = writingTracks.Last(t => t.TrackSession == i); - - alcSessions.Add(i, - new AlcoholSession - { - sessionStart = (int)firstTrack.TrackStartSector - 150, - sessionEnd = (int)lastTrack.TrackEndSector + 1, - sessionSequence = (ushort)i, - allBlocks = - (byte)(decodedToc?.TrackDescriptors.Count(t => t.SessionNumber == i) ?? - writingTracks.Count(t => t.TrackSession == i) + 3), - nonTrackBlocks = - (byte)(decodedToc?.TrackDescriptors.Count(t => t.SessionNumber == i && - t.POINT >= 0xA0 && - t.POINT <= 0xAF) ?? - 3), - firstTrack = (ushort)firstTrack.TrackSequence, - lastTrack = (ushort)lastTrack.TrackSequence, - trackOffset = (uint)currentTrackOffset - }); - - Dictionary thisSessionTracks = new Dictionary(); - trackFlags.TryGetValue((byte)firstTrack.TrackSequence, out byte firstTrackControl); - trackFlags.TryGetValue((byte)lastTrack.TrackSequence, out byte lastTrackControl); - if(firstTrackControl == 0 && firstTrack.TrackType != TrackType.Audio) - firstTrackControl = (byte)CdFlags.DataTrack; - if(lastTrackControl == 0 && lastTrack.TrackType != TrackType.Audio) - lastTrackControl = (byte)CdFlags.DataTrack; - (byte minute, byte second, byte frame) leadinPmsf = LbaToMsf(lastTrack.TrackEndSector + 1); - - if(decodedToc.HasValue && - decodedToc.Value.TrackDescriptors.Any(t => t.SessionNumber == i && t.POINT >= 0xA0 && - t.POINT <= 0xAF)) - foreach(FullTOC.TrackDataDescriptor tocTrk in - decodedToc.Value.TrackDescriptors.Where(t => t.SessionNumber == i && t.POINT >= 0xA0 && - t.POINT <= 0xAF)) - { - thisSessionTracks.Add(tocTrk.POINT, - new AlcoholTrack - { - adrCtl = (byte)((tocTrk.ADR << 4) + tocTrk.CONTROL), - tno = tocTrk.TNO, - point = tocTrk.POINT, - min = tocTrk.Min, - sec = tocTrk.Sec, - frame = tocTrk.Frame, - zero = tocTrk.Zero, - pmin = tocTrk.PMIN, - psec = tocTrk.PSEC, - pframe = tocTrk.PFRAME, - mode = AlcoholTrackMode.NoData, - unknown = new byte[18], - unknown2 = new byte[24] - }); - currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)); - } - else - { - thisSessionTracks.Add(0xA0, new AlcoholTrack - { - adrCtl = (byte)((1 << 4) + firstTrackControl), - pmin = (byte)firstTrack.TrackSequence, - mode = AlcoholTrackMode.NoData, - point = 0xA0, - unknown = new byte[18], - unknown2 = new byte[24], - psec = (byte)(imageInfo.MediaType == MediaType.CDI - ? 0x10 - : writingTracks.Any(t => t.TrackType == TrackType.CdMode2Form1 || - t.TrackType == TrackType.CdMode2Form2 || - t.TrackType == TrackType.CdMode2Formless) - ? 0x20 - : 0) - }); - - thisSessionTracks.Add(0xA1, - new AlcoholTrack - { - adrCtl = (byte)((1 << 4) + lastTrackControl), - pmin = (byte)lastTrack.TrackSequence, - mode = AlcoholTrackMode.NoData, - point = 0xA1, - unknown = new byte[18], - unknown2 = new byte[24] - }); - - thisSessionTracks.Add(0xA2, - new AlcoholTrack - { - adrCtl = (byte)((1 << 4) + firstTrackControl), - zero = 0, - pmin = leadinPmsf.minute, - psec = leadinPmsf.second, - pframe = leadinPmsf.frame, - mode = AlcoholTrackMode.NoData, - point = 0xA2, - unknown = new byte[18], - unknown2 = new byte[24] - }); - currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 3; - } - - foreach(Track track in writingTracks.Where(t => t.TrackSession == i).OrderBy(t => t.TrackSequence)) - { - AlcoholTrack alcTrk = new AlcoholTrack(); - if(decodedToc.HasValue && - decodedToc.Value.TrackDescriptors.Any(t => t.SessionNumber == i && - t.POINT == track.TrackSequence)) - { - FullTOC.TrackDataDescriptor tocTrk = - decodedToc.Value.TrackDescriptors.First(t => t.SessionNumber == i && - t.POINT == track.TrackSequence); - - alcTrk.adrCtl = (byte)((tocTrk.ADR << 4) + tocTrk.CONTROL); - alcTrk.tno = tocTrk.TNO; - alcTrk.point = tocTrk.POINT; - alcTrk.min = tocTrk.Min; - alcTrk.sec = tocTrk.Sec; - alcTrk.frame = tocTrk.Frame; - alcTrk.zero = tocTrk.Zero; - alcTrk.pmin = tocTrk.PMIN; - alcTrk.psec = tocTrk.PSEC; - alcTrk.pframe = tocTrk.PFRAME; - } - else - { - (byte minute, byte second, byte frame) msf = LbaToMsf(track.TrackStartSector); - trackFlags.TryGetValue((byte)track.TrackSequence, out byte trackControl); - if(trackControl == 0 && track.TrackType != TrackType.Audio) - trackControl = (byte)CdFlags.DataTrack; - - alcTrk.adrCtl = (byte)((1 << 4) + trackControl); - alcTrk.point = (byte)track.TrackSequence; - alcTrk.zero = 0; - alcTrk.pmin = msf.minute; - alcTrk.psec = msf.second; - alcTrk.pframe = msf.frame; - } - - alcTrk.mode = TrackTypeToAlcohol(track.TrackType); - alcTrk.subMode = track.TrackSubchannelType != TrackSubchannelType.None - ? AlcoholSubchannelMode.Interleaved - : AlcoholSubchannelMode.None; - alcTrk.sectorSize = (ushort)(track.TrackRawBytesPerSector + - (track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0)); - alcTrk.startLba = (uint)track.TrackStartSector; - alcTrk.startOffset = track.TrackFileOffset; - alcTrk.files = 1; - alcTrk.extraOffset = (uint)currentExtraOffset; - alcTrk.footerOffset = (uint)footerOffset; - // Alcohol seems to set that for all CD tracks - // Daemon Tools expect it to be like this - alcTrk.unknown = new byte[] - { - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00 - }; - alcTrk.unknown2 = new byte[24]; - - thisSessionTracks.Add((int)track.TrackSequence, alcTrk); - - currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)); - currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrackExtra)); - - AlcoholTrackExtra trkExtra = new AlcoholTrackExtra - { - sectors = (uint)(track.TrackEndSector - track.TrackStartSector + 1) - }; - - // When track mode changes there's a mandatory gap, Alcohol needs it - if(track.TrackSequence == firstTrack.TrackSequence) trkExtra.pregap = 150; - else if(thisSessionTracks.TryGetValue((int)(track.TrackSequence - 1), - out AlcoholTrack previousTrack) && - alcTrackExtras.TryGetValue((int)(track.TrackSequence - 1), - out AlcoholTrackExtra previousExtra) && - previousTrack.mode != alcTrk.mode) - { - previousExtra.sectors -= 150; - trkExtra.pregap = 150; - alcTrackExtras.Remove((int)(track.TrackSequence - 1)); - alcTrackExtras.Add((int)(track.TrackSequence - 1), previousExtra); - } - else trkExtra.pregap = 0; - - alcTrackExtras.Add((int)track.TrackSequence, trkExtra); - } - - if(decodedToc.HasValue && - decodedToc.Value.TrackDescriptors.Any(t => t.SessionNumber == i && t.POINT >= 0xB0)) - foreach(FullTOC.TrackDataDescriptor tocTrk in - decodedToc.Value.TrackDescriptors.Where(t => t.SessionNumber == i && t.POINT >= 0xB0)) - { - thisSessionTracks.Add(tocTrk.POINT, - new AlcoholTrack - { - adrCtl = (byte)((tocTrk.ADR << 4) + tocTrk.CONTROL), - tno = tocTrk.TNO, - point = tocTrk.POINT, - min = tocTrk.Min, - sec = tocTrk.Sec, - frame = tocTrk.Frame, - zero = tocTrk.Zero, - pmin = tocTrk.PMIN, - psec = tocTrk.PSEC, - pframe = tocTrk.PFRAME, - mode = AlcoholTrackMode.NoData, - unknown = new byte[18], - unknown2 = new byte[24] - }); - currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)); - } - else if(i < sessions) - { - (byte minute, byte second, byte frame) leadoutAmsf = - LbaToMsf(writingTracks.First(t => t.TrackSession == i + 1).TrackStartSector - 150); - (byte minute, byte second, byte frame) leadoutPmsf = - LbaToMsf(writingTracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).Last() - .TrackStartSector); - - thisSessionTracks.Add(0xB0, - new AlcoholTrack - { - point = 0xB0, - adrCtl = 0x50, - zero = 0, - min = leadoutAmsf.minute, - sec = leadoutAmsf.second, - frame = leadoutAmsf.frame, - pmin = leadoutPmsf.minute, - psec = leadoutPmsf.second, - pframe = leadoutPmsf.frame, - unknown = new byte[18], - unknown2 = new byte[24] - }); - - thisSessionTracks.Add(0xC0, - new AlcoholTrack - { - point = 0xC0, - adrCtl = 0x50, - min = 128, - pmin = 97, - psec = 25, - unknown = new byte[18], - unknown2 = new byte[24] - }); - - currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 2; - } - - alcToc.Add(i, thisSessionTracks); - } - - alcFooter = new AlcoholFooter - { - filenameOffset = (uint)(footerOffset + Marshal.SizeOf(typeof(AlcoholFooter))), - widechar = 1 - }; - - byte[] filename = Encoding.Unicode.GetBytes("*.mdf"); // Yup, Alcohol stores no filename but a wildcard. - - IntPtr blockPtr; - - // Write header - descriptorStream.Seek(0, SeekOrigin.Begin); - byte[] block = new byte[Marshal.SizeOf(header)]; - blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.StructureToPtr(header, blockPtr, true); - Marshal.Copy(blockPtr, block, 0, block.Length); - Marshal.FreeHGlobal(blockPtr); - descriptorStream.Write(block, 0, block.Length); - - // Write DVD structures if pressent - if(header.structuresOffset != 0) - { - if(dmi != null) - { - descriptorStream.Seek(header.structuresOffset, SeekOrigin.Begin); - if(dmi.Length == 2052) descriptorStream.Write(dmi, 0, 2052); - else if(dmi.Length == 2048) - { - descriptorStream.Write(new byte[] {0x08, 0x02, 0x00, 0x00}, 0, 4); - descriptorStream.Write(dmi, 0, 2048); - } - } - - // TODO: Create fake PFI if none present - if(pfi != null) - { - descriptorStream.Seek(header.structuresOffset + 2052, SeekOrigin.Begin); - descriptorStream.Write(pfi, pfi.Length - 2048, 2048); - } - } - - // Write sessions - descriptorStream.Seek(header.sessionOffset, SeekOrigin.Begin); - foreach(AlcoholSession session in alcSessions.Values) - { - block = new byte[Marshal.SizeOf(session)]; - blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(session)); - Marshal.StructureToPtr(session, blockPtr, true); - Marshal.Copy(blockPtr, block, 0, block.Length); - Marshal.FreeHGlobal(blockPtr); - descriptorStream.Write(block, 0, block.Length); - } - - // Write tracks - foreach(KeyValuePair> kvp in alcToc) - { - descriptorStream.Seek(alcSessions.First(t => t.Key == kvp.Key).Value.trackOffset, SeekOrigin.Begin); - foreach(AlcoholTrack track in kvp.Value.Values) - { - block = new byte[Marshal.SizeOf(track)]; - blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(track)); - Marshal.StructureToPtr(track, blockPtr, true); - Marshal.Copy(blockPtr, block, 0, block.Length); - Marshal.FreeHGlobal(blockPtr); - descriptorStream.Write(block, 0, block.Length); - - if(isDvd) continue; - - // Write extra - long position = descriptorStream.Position; - descriptorStream.Seek(track.extraOffset, SeekOrigin.Begin); - if(alcTrackExtras.TryGetValue(track.point, out AlcoholTrackExtra extra)) - { - block = new byte[Marshal.SizeOf(extra)]; - blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(extra)); - Marshal.StructureToPtr(extra, blockPtr, true); - Marshal.Copy(blockPtr, block, 0, block.Length); - Marshal.FreeHGlobal(blockPtr); - descriptorStream.Write(block, 0, block.Length); - } - - descriptorStream.Seek(position, SeekOrigin.Begin); - } - } - - // Write BCA - if(bca != null) - { - descriptorStream.Seek(header.bcaOffset, SeekOrigin.Begin); - descriptorStream.Write(bca, 0, bca.Length); - } - - // Write footer - descriptorStream.Seek(footerOffset, SeekOrigin.Begin); - block = new byte[Marshal.SizeOf(alcFooter)]; - blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(alcFooter)); - Marshal.StructureToPtr(alcFooter, blockPtr, true); - Marshal.Copy(blockPtr, block, 0, block.Length); - Marshal.FreeHGlobal(blockPtr); - descriptorStream.Write(block, 0, block.Length); - - // Write filename - descriptorStream.Write(filename, 0, filename.Length); - // Write filename null termination - descriptorStream.Write(new byte[] {0, 0}, 0, 2); - - descriptorStream.Flush(); - descriptorStream.Close(); - imageStream.Flush(); - imageStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - { - if(data.Length != 1) - { - ErrorMessage = "Incorrect data size for track flags"; - return false; - } - - trackFlags.Add((byte)track.TrackSequence, data[0]); - - return true; - } - case SectorTagType.CdSectorSubchannel: - { - if(track.TrackSubchannelType == 0) - { - ErrorMessage = - $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; - return false; - } - - if(data.Length != 96) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - imageStream - .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, - SeekOrigin.Begin); - imageStream.Write(data, 0, data.Length); - - return true; - } - default: - ErrorMessage = $"Unsupported tag type {tag}"; - return false; - } - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: return WriteSectorTag(data, sectorAddress, tag); - case SectorTagType.CdSectorSubchannel: - { - if(track.TrackSubchannelType == 0) - { - ErrorMessage = - $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; - return false; - } - - if(data.Length % 96 != 0) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - for(uint i = 0; i < length; i++) - { - imageStream - .Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, - SeekOrigin.Begin); - imageStream.Write(data, (int)(i * 96), 96); - } - - return true; - } - default: - ErrorMessage = $"Unsupported tag type {tag}"; - return false; - } - } - - public bool SetDumpHardware(List dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - static ushort AlcoholTrackModeToBytesPerSector(AlcoholTrackMode trackMode) - { - switch(trackMode) - { - case AlcoholTrackMode.Audio: - case AlcoholTrackMode.Mode1: - case AlcoholTrackMode.Mode2: - case AlcoholTrackMode.Mode2F1: - case AlcoholTrackMode.Mode2F2: - case AlcoholTrackMode.Mode2F2Alt: - case AlcoholTrackMode.Mode2F1Alt: return 2352; - case AlcoholTrackMode.DVD: return 2048; - default: return 0; - } - } - - static ushort AlcoholTrackModeToCookedBytesPerSector(AlcoholTrackMode trackMode) - { - switch(trackMode) - { - case AlcoholTrackMode.Mode1: - case AlcoholTrackMode.Mode2F1: - case AlcoholTrackMode.Mode2F1Alt: return 2048; - case AlcoholTrackMode.Mode2F2: - case AlcoholTrackMode.Mode2F2Alt: return 2324; - case AlcoholTrackMode.Mode2: return 2336; - case AlcoholTrackMode.Audio: return 2352; - case AlcoholTrackMode.DVD: return 2048; - default: return 0; - } - } - - static TrackType AlcoholTrackTypeToTrackType(AlcoholTrackMode trackType) - { - switch(trackType) - { - case AlcoholTrackMode.Mode1: return TrackType.CdMode1; - case AlcoholTrackMode.Mode2F1: - case AlcoholTrackMode.Mode2F1Alt: return TrackType.CdMode2Form1; - case AlcoholTrackMode.Mode2F2: - case AlcoholTrackMode.Mode2F2Alt: return TrackType.CdMode2Form2; - case AlcoholTrackMode.Mode2: return TrackType.CdMode2Formless; - case AlcoholTrackMode.Audio: return TrackType.Audio; - default: return TrackType.Data; - } - } - - static MediaType AlcoholMediumTypeToMediaType(AlcoholMediumType discType) - { - switch(discType) - { - case AlcoholMediumType.CD: return MediaType.CD; - case AlcoholMediumType.CDR: return MediaType.CDR; - case AlcoholMediumType.CDRW: return MediaType.CDRW; - case AlcoholMediumType.DVD: return MediaType.DVDROM; - case AlcoholMediumType.DVDR: return MediaType.DVDR; - default: return MediaType.Unknown; - } - } - - static AlcoholMediumType MediaTypeToAlcohol(MediaType type) - { - switch(type) - { - case MediaType.CD: - case MediaType.CDDA: - case MediaType.CDEG: - case MediaType.CDG: - case MediaType.CDI: - case MediaType.CDMIDI: - case MediaType.CDPLUS: - case MediaType.CDROM: - case MediaType.CDROMXA: - case MediaType.CDV: - case MediaType.DTSCD: - case MediaType.JaguarCD: - case MediaType.MEGACD: - case MediaType.PS1CD: - case MediaType.PS2CD: - case MediaType.SuperCDROM2: - case MediaType.SVCD: - case MediaType.SATURNCD: - case MediaType.ThreeDO: - case MediaType.VCD: - case MediaType.VCDHD: return AlcoholMediumType.CD; - case MediaType.CDR: return AlcoholMediumType.CDR; - case MediaType.CDRW: - case MediaType.CDMRW: return AlcoholMediumType.CDRW; - case MediaType.DVDR: - case MediaType.DVDRW: - case MediaType.DVDPR: - case MediaType.DVDRDL: - case MediaType.DVDRWDL: - case MediaType.DVDPRDL: - case MediaType.DVDPRWDL: return AlcoholMediumType.DVDR; - default: return AlcoholMediumType.DVD; - } - } - - static AlcoholTrackMode TrackTypeToAlcohol(TrackType type) - { - switch(type) - { - case TrackType.Audio: return AlcoholTrackMode.Audio; - case TrackType.CdMode1: return AlcoholTrackMode.Mode1; - case TrackType.CdMode2Formless: return AlcoholTrackMode.Mode2; - case TrackType.CdMode2Form1: return AlcoholTrackMode.Mode2F1; - case TrackType.CdMode2Form2: return AlcoholTrackMode.Mode2F2; - default: return AlcoholTrackMode.DVD; - } - } - - static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) - { - return ((byte)((sector + 150) / 75 / 60), (byte)((sector + 150) / 75 % 60), (byte)((sector + 150) % 75)); - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholHeader - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] signature; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public byte[] version; - public AlcoholMediumType type; - public ushort sessions; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public ushort[] unknown1; - public ushort bcaLength; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] unknown2; - public uint bcaOffset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - public uint[] unknown3; - public uint structuresOffset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public uint[] unknown4; - public uint sessionOffset; - public uint dpmOffset; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholSession - { - public int sessionStart; - public int sessionEnd; - public ushort sessionSequence; - public byte allBlocks; - public byte nonTrackBlocks; - public ushort firstTrack; - public ushort lastTrack; - public uint unknown; - public uint trackOffset; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholTrack - { - public AlcoholTrackMode mode; - public AlcoholSubchannelMode subMode; - public byte adrCtl; - public byte tno; - public byte point; - public byte min; - public byte sec; - public byte frame; - public byte zero; - public byte pmin; - public byte psec; - public byte pframe; - public uint extraOffset; - public ushort sectorSize; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)] - public byte[] unknown; - public uint startLba; - public ulong startOffset; - public uint files; - public uint footerOffset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] - public byte[] unknown2; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholTrackExtra - { - public uint pregap; - public uint sectors; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholFooter - { - public uint filenameOffset; - public uint widechar; - public uint unknown1; - public uint unknown2; - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - enum AlcoholMediumType : ushort - { - CD = 0x00, - CDR = 0x01, - CDRW = 0x02, - DVD = 0x10, - DVDR = 0x12 - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - enum AlcoholTrackMode : byte - { - NoData = 0x00, - DVD = 0x02, - Audio = 0xA9, - Mode1 = 0xAA, - Mode2 = 0xAB, - Mode2F1 = 0xEC, - Mode2F2 = 0xED, - Mode2F1Alt = 0xAC, - Mode2F2Alt = 0xAD - } - - enum AlcoholSubchannelMode : byte - { - None = 0x00, - Interleaved = 0x08 - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120/Structs.cs b/DiscImageChef.DiscImages/Alcohol120/Structs.cs new file mode 100644 index 000000000..c1cb6a241 --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Structs.cs @@ -0,0 +1,120 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct AlcoholHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] signature; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] version; + public AlcoholMediumType type; + public ushort sessions; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public ushort[] unknown1; + public ushort bcaLength; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] unknown2; + public uint bcaOffset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + public uint[] unknown3; + public uint structuresOffset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public uint[] unknown4; + public uint sessionOffset; + public uint dpmOffset; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct AlcoholSession + { + public int sessionStart; + public int sessionEnd; + public ushort sessionSequence; + public byte allBlocks; + public byte nonTrackBlocks; + public ushort firstTrack; + public ushort lastTrack; + public uint unknown; + public uint trackOffset; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct AlcoholTrack + { + public AlcoholTrackMode mode; + public AlcoholSubchannelMode subMode; + public byte adrCtl; + public byte tno; + public byte point; + public byte min; + public byte sec; + public byte frame; + public byte zero; + public byte pmin; + public byte psec; + public byte pframe; + public uint extraOffset; + public ushort sectorSize; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)] + public byte[] unknown; + public uint startLba; + public ulong startOffset; + public uint files; + public uint footerOffset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] + public byte[] unknown2; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct AlcoholTrackExtra + { + public uint pregap; + public uint sectors; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct AlcoholFooter + { + public uint filenameOffset; + public uint widechar; + public uint unknown1; + public uint unknown2; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Alcohol120/Write.cs b/DiscImageChef.DiscImages/Alcohol120/Write.cs new file mode 100644 index 000000000..7562c518c --- /dev/null +++ b/DiscImageChef.DiscImages/Alcohol120/Write.cs @@ -0,0 +1,985 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Alcohol 120% disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Decoders.CD; +using Schemas; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + public partial class Alcohol120 + { + public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, + uint sectorSize) + { + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try + { + descriptorStream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None); + imageStream = + new + FileStream(Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)) + ".mdf", + FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + imageInfo.MediaType = mediaType; + + switch(mediaType) + { + case MediaType.CD: + case MediaType.CDDA: + case MediaType.CDEG: + case MediaType.CDG: + case MediaType.CDI: + case MediaType.CDMIDI: + case MediaType.CDMRW: + case MediaType.CDPLUS: + case MediaType.CDR: + case MediaType.CDROM: + case MediaType.CDROMXA: + case MediaType.CDRW: + case MediaType.CDV: + case MediaType.DTSCD: + case MediaType.JaguarCD: + case MediaType.MEGACD: + case MediaType.PS1CD: + case MediaType.PS2CD: + case MediaType.SuperCDROM2: + case MediaType.SVCD: + case MediaType.SATURNCD: + case MediaType.ThreeDO: + case MediaType.VCD: + case MediaType.VCDHD: + isDvd = false; + break; + default: + isDvd = true; + break; + } + + trackFlags = new Dictionary(); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + switch(tag) + { + case MediaTagType.CD_FullTOC: + if(isDvd) + { + ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; + return false; + } + + byte[] fullTocSize = BigEndianBitConverter.GetBytes((short)data.Length); + fullToc = new byte[data.Length + 2]; + Array.Copy(data, 0, fullToc, 2, data.Length); + fullToc[0] = fullTocSize[0]; + fullToc[1] = fullTocSize[1]; + return true; + case MediaTagType.DVD_PFI: + if(!isDvd) + { + ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; + return false; + } + + pfi = data; + return true; + case MediaTagType.DVD_DMI: + if(!isDvd) + { + ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; + return false; + } + + dmi = data; + return true; + case MediaTagType.DVD_BCA: + if(!isDvd) + { + ErrorMessage = $"Unsupported media tag {tag} for medium type {imageInfo.MediaType}"; + return false; + } + + bca = data; + return true; + default: + ErrorMessage = $"Unsupported media tag {tag}"; + return false; + } + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) + { + ErrorMessage = "Invalid write mode for this sector"; + return false; + } + + if(data.Length != track.TrackRawBytesPerSector) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + imageStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + imageStream.Write(data, 0, data.Length); + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) + { + ErrorMessage = "Invalid write mode for this sector"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + if(data.Length % track.TrackRawBytesPerSector != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + switch(track.TrackSubchannelType) + { + case TrackSubchannelType.None: + imageStream + .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + imageStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + case TrackSubchannelType.Raw: + case TrackSubchannelType.RawInterleaved: + imageStream + .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)), + SeekOrigin.Begin); + for(uint i = 0; i < length; i++) + { + imageStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); + imageStream.Position += 96; + } + + ErrorMessage = ""; + return true; + default: + ErrorMessage = "Invalid subchannel mode for this sector"; + return false; + } + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(data.Length != track.TrackRawBytesPerSector) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); + + imageStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), + SeekOrigin.Begin); + imageStream.Write(data, 0, data.Length); + + return true; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + if(data.Length % track.TrackRawBytesPerSector != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); + + for(uint i = 0; i < length; i++) + { + imageStream.Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), + SeekOrigin.Begin); + imageStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); + } + + return true; + } + + public bool SetTracks(List tracks) + { + ulong currentDataOffset = 0; + + writingTracks = new List(); + foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) + { + Track newTrack = track; + uint subchannelSize; + switch(track.TrackSubchannelType) + { + case TrackSubchannelType.None: + subchannelSize = 0; + break; + case TrackSubchannelType.Raw: + case TrackSubchannelType.RawInterleaved: + subchannelSize = 96; + break; + default: + ErrorMessage = $"Unsupported subchannel type {track.TrackSubchannelType}"; + return false; + } + + newTrack.TrackFileOffset = currentDataOffset; + + currentDataOffset += (ulong)(newTrack.TrackRawBytesPerSector + subchannelSize) * + (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); + + writingTracks.Add(newTrack); + } + + return true; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + byte sessions = byte.MinValue; + + foreach(Track t in writingTracks) + if(t.TrackSession > byte.MinValue) + sessions = (byte)t.TrackSession; + + AlcoholHeader header = new AlcoholHeader + { + signature = alcoholSignature, + version = new byte[] {1, 5}, + type = MediaTypeToAlcohol(imageInfo.MediaType), + sessions = sessions, + structuresOffset = (uint)(pfi == null ? 0 : 96), + sessionOffset = (uint)(pfi == null ? 96 : 4196), + unknown1 = new ushort[2], + unknown2 = new uint[2], + unknown3 = new uint[6], + unknown4 = new uint[3] + }; + // Alcohol sets this always, Daemon Tool expects this + header.unknown1[0] = 2; + + alcSessions = new Dictionary(); + alcTracks = new Dictionary(); + alcToc = new Dictionary>(); + writingTracks = writingTracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).ToList(); + alcTrackExtras = new Dictionary(); + long currentTrackOffset = header.sessionOffset + Marshal.SizeOf(typeof(AlcoholSession)) * sessions; + + FullTOC.CDFullTOC? decodedToc = FullTOC.Decode(fullToc); + + long currentExtraOffset = currentTrackOffset; + for(int i = 1; i <= sessions; i++) + if(decodedToc.HasValue) + currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * + decodedToc.Value.TrackDescriptors.Count(t => t.SessionNumber == i); + else + { + currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 3; + currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * + writingTracks.Count(t => t.TrackSession == i); + if(i < sessions) currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 2; + } + + long footerOffset = currentExtraOffset + Marshal.SizeOf(typeof(AlcoholTrackExtra)) * writingTracks.Count; + if(bca != null) + { + header.bcaOffset = (uint)footerOffset; + footerOffset += bca.Length; + } + + if(isDvd) + { + alcSessions.Add(1, + new AlcoholSession + { + sessionEnd = + (int)(writingTracks[0].TrackEndSector - writingTracks[0].TrackStartSector + 1), + sessionSequence = 1, + allBlocks = 1, + nonTrackBlocks = 3, + firstTrack = 1, + lastTrack = 1, + trackOffset = 4220 + }); + + footerOffset = 4300; + if(bca != null) footerOffset += bca.Length; + + alcTracks.Add(1, + new AlcoholTrack + { + mode = AlcoholTrackMode.DVD, + adrCtl = 20, + point = 1, + extraOffset = + (uint)(writingTracks[0].TrackEndSector - writingTracks[0].TrackStartSector + 1), + sectorSize = 2048, + files = 1, + footerOffset = (uint)footerOffset, + unknown = new byte[18], + unknown2 = new byte[24] + }); + + alcToc.Add(1, alcTracks); + } + else + for(int i = 1; i <= sessions; i++) + { + Track firstTrack = writingTracks.First(t => t.TrackSession == i); + Track lastTrack = writingTracks.Last(t => t.TrackSession == i); + + alcSessions.Add(i, + new AlcoholSession + { + sessionStart = (int)firstTrack.TrackStartSector - 150, + sessionEnd = (int)lastTrack.TrackEndSector + 1, + sessionSequence = (ushort)i, + allBlocks = + (byte)(decodedToc?.TrackDescriptors.Count(t => t.SessionNumber == i) ?? + writingTracks.Count(t => t.TrackSession == i) + 3), + nonTrackBlocks = + (byte)(decodedToc?.TrackDescriptors.Count(t => t.SessionNumber == i && + t.POINT >= 0xA0 && + t.POINT <= 0xAF) ?? + 3), + firstTrack = (ushort)firstTrack.TrackSequence, + lastTrack = (ushort)lastTrack.TrackSequence, + trackOffset = (uint)currentTrackOffset + }); + + Dictionary thisSessionTracks = new Dictionary(); + trackFlags.TryGetValue((byte)firstTrack.TrackSequence, out byte firstTrackControl); + trackFlags.TryGetValue((byte)lastTrack.TrackSequence, out byte lastTrackControl); + if(firstTrackControl == 0 && firstTrack.TrackType != TrackType.Audio) + firstTrackControl = (byte)CdFlags.DataTrack; + if(lastTrackControl == 0 && lastTrack.TrackType != TrackType.Audio) + lastTrackControl = (byte)CdFlags.DataTrack; + (byte minute, byte second, byte frame) leadinPmsf = LbaToMsf(lastTrack.TrackEndSector + 1); + + if(decodedToc.HasValue && + decodedToc.Value.TrackDescriptors.Any(t => t.SessionNumber == i && t.POINT >= 0xA0 && + t.POINT <= 0xAF)) + foreach(FullTOC.TrackDataDescriptor tocTrk in + decodedToc.Value.TrackDescriptors.Where(t => t.SessionNumber == i && t.POINT >= 0xA0 && + t.POINT <= 0xAF)) + { + thisSessionTracks.Add(tocTrk.POINT, + new AlcoholTrack + { + adrCtl = (byte)((tocTrk.ADR << 4) + tocTrk.CONTROL), + tno = tocTrk.TNO, + point = tocTrk.POINT, + min = tocTrk.Min, + sec = tocTrk.Sec, + frame = tocTrk.Frame, + zero = tocTrk.Zero, + pmin = tocTrk.PMIN, + psec = tocTrk.PSEC, + pframe = tocTrk.PFRAME, + mode = AlcoholTrackMode.NoData, + unknown = new byte[18], + unknown2 = new byte[24] + }); + currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)); + } + else + { + thisSessionTracks.Add(0xA0, new AlcoholTrack + { + adrCtl = (byte)((1 << 4) + firstTrackControl), + pmin = (byte)firstTrack.TrackSequence, + mode = AlcoholTrackMode.NoData, + point = 0xA0, + unknown = new byte[18], + unknown2 = new byte[24], + psec = (byte)(imageInfo.MediaType == MediaType.CDI + ? 0x10 + : writingTracks.Any(t => t.TrackType == TrackType.CdMode2Form1 || + t.TrackType == TrackType.CdMode2Form2 || + t.TrackType == TrackType.CdMode2Formless) + ? 0x20 + : 0) + }); + + thisSessionTracks.Add(0xA1, + new AlcoholTrack + { + adrCtl = (byte)((1 << 4) + lastTrackControl), + pmin = (byte)lastTrack.TrackSequence, + mode = AlcoholTrackMode.NoData, + point = 0xA1, + unknown = new byte[18], + unknown2 = new byte[24] + }); + + thisSessionTracks.Add(0xA2, + new AlcoholTrack + { + adrCtl = (byte)((1 << 4) + firstTrackControl), + zero = 0, + pmin = leadinPmsf.minute, + psec = leadinPmsf.second, + pframe = leadinPmsf.frame, + mode = AlcoholTrackMode.NoData, + point = 0xA2, + unknown = new byte[18], + unknown2 = new byte[24] + }); + currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 3; + } + + foreach(Track track in writingTracks.Where(t => t.TrackSession == i).OrderBy(t => t.TrackSequence)) + { + AlcoholTrack alcTrk = new AlcoholTrack(); + if(decodedToc.HasValue && + decodedToc.Value.TrackDescriptors.Any(t => t.SessionNumber == i && + t.POINT == track.TrackSequence)) + { + FullTOC.TrackDataDescriptor tocTrk = + decodedToc.Value.TrackDescriptors.First(t => t.SessionNumber == i && + t.POINT == track.TrackSequence); + + alcTrk.adrCtl = (byte)((tocTrk.ADR << 4) + tocTrk.CONTROL); + alcTrk.tno = tocTrk.TNO; + alcTrk.point = tocTrk.POINT; + alcTrk.min = tocTrk.Min; + alcTrk.sec = tocTrk.Sec; + alcTrk.frame = tocTrk.Frame; + alcTrk.zero = tocTrk.Zero; + alcTrk.pmin = tocTrk.PMIN; + alcTrk.psec = tocTrk.PSEC; + alcTrk.pframe = tocTrk.PFRAME; + } + else + { + (byte minute, byte second, byte frame) msf = LbaToMsf(track.TrackStartSector); + trackFlags.TryGetValue((byte)track.TrackSequence, out byte trackControl); + if(trackControl == 0 && track.TrackType != TrackType.Audio) + trackControl = (byte)CdFlags.DataTrack; + + alcTrk.adrCtl = (byte)((1 << 4) + trackControl); + alcTrk.point = (byte)track.TrackSequence; + alcTrk.zero = 0; + alcTrk.pmin = msf.minute; + alcTrk.psec = msf.second; + alcTrk.pframe = msf.frame; + } + + alcTrk.mode = TrackTypeToAlcohol(track.TrackType); + alcTrk.subMode = track.TrackSubchannelType != TrackSubchannelType.None + ? AlcoholSubchannelMode.Interleaved + : AlcoholSubchannelMode.None; + alcTrk.sectorSize = (ushort)(track.TrackRawBytesPerSector + + (track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0)); + alcTrk.startLba = (uint)track.TrackStartSector; + alcTrk.startOffset = track.TrackFileOffset; + alcTrk.files = 1; + alcTrk.extraOffset = (uint)currentExtraOffset; + alcTrk.footerOffset = (uint)footerOffset; + // Alcohol seems to set that for all CD tracks + // Daemon Tools expect it to be like this + alcTrk.unknown = new byte[] + { + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + }; + alcTrk.unknown2 = new byte[24]; + + thisSessionTracks.Add((int)track.TrackSequence, alcTrk); + + currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)); + currentExtraOffset += Marshal.SizeOf(typeof(AlcoholTrackExtra)); + + AlcoholTrackExtra trkExtra = new AlcoholTrackExtra + { + sectors = (uint)(track.TrackEndSector - track.TrackStartSector + 1) + }; + + // When track mode changes there's a mandatory gap, Alcohol needs it + if(track.TrackSequence == firstTrack.TrackSequence) trkExtra.pregap = 150; + else if(thisSessionTracks.TryGetValue((int)(track.TrackSequence - 1), + out AlcoholTrack previousTrack) && + alcTrackExtras.TryGetValue((int)(track.TrackSequence - 1), + out AlcoholTrackExtra previousExtra) && + previousTrack.mode != alcTrk.mode) + { + previousExtra.sectors -= 150; + trkExtra.pregap = 150; + alcTrackExtras.Remove((int)(track.TrackSequence - 1)); + alcTrackExtras.Add((int)(track.TrackSequence - 1), previousExtra); + } + else trkExtra.pregap = 0; + + alcTrackExtras.Add((int)track.TrackSequence, trkExtra); + } + + if(decodedToc.HasValue && + decodedToc.Value.TrackDescriptors.Any(t => t.SessionNumber == i && t.POINT >= 0xB0)) + foreach(FullTOC.TrackDataDescriptor tocTrk in + decodedToc.Value.TrackDescriptors.Where(t => t.SessionNumber == i && t.POINT >= 0xB0)) + { + thisSessionTracks.Add(tocTrk.POINT, + new AlcoholTrack + { + adrCtl = (byte)((tocTrk.ADR << 4) + tocTrk.CONTROL), + tno = tocTrk.TNO, + point = tocTrk.POINT, + min = tocTrk.Min, + sec = tocTrk.Sec, + frame = tocTrk.Frame, + zero = tocTrk.Zero, + pmin = tocTrk.PMIN, + psec = tocTrk.PSEC, + pframe = tocTrk.PFRAME, + mode = AlcoholTrackMode.NoData, + unknown = new byte[18], + unknown2 = new byte[24] + }); + currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)); + } + else if(i < sessions) + { + (byte minute, byte second, byte frame) leadoutAmsf = + LbaToMsf(writingTracks.First(t => t.TrackSession == i + 1).TrackStartSector - 150); + (byte minute, byte second, byte frame) leadoutPmsf = + LbaToMsf(writingTracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).Last() + .TrackStartSector); + + thisSessionTracks.Add(0xB0, + new AlcoholTrack + { + point = 0xB0, + adrCtl = 0x50, + zero = 0, + min = leadoutAmsf.minute, + sec = leadoutAmsf.second, + frame = leadoutAmsf.frame, + pmin = leadoutPmsf.minute, + psec = leadoutPmsf.second, + pframe = leadoutPmsf.frame, + unknown = new byte[18], + unknown2 = new byte[24] + }); + + thisSessionTracks.Add(0xC0, + new AlcoholTrack + { + point = 0xC0, + adrCtl = 0x50, + min = 128, + pmin = 97, + psec = 25, + unknown = new byte[18], + unknown2 = new byte[24] + }); + + currentTrackOffset += Marshal.SizeOf(typeof(AlcoholTrack)) * 2; + } + + alcToc.Add(i, thisSessionTracks); + } + + alcFooter = new AlcoholFooter + { + filenameOffset = (uint)(footerOffset + Marshal.SizeOf(typeof(AlcoholFooter))), + widechar = 1 + }; + + byte[] filename = Encoding.Unicode.GetBytes("*.mdf"); // Yup, Alcohol stores no filename but a wildcard. + + IntPtr blockPtr; + + // Write header + descriptorStream.Seek(0, SeekOrigin.Begin); + byte[] block = new byte[Marshal.SizeOf(header)]; + blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.StructureToPtr(header, blockPtr, true); + Marshal.Copy(blockPtr, block, 0, block.Length); + Marshal.FreeHGlobal(blockPtr); + descriptorStream.Write(block, 0, block.Length); + + // Write DVD structures if pressent + if(header.structuresOffset != 0) + { + if(dmi != null) + { + descriptorStream.Seek(header.structuresOffset, SeekOrigin.Begin); + if(dmi.Length == 2052) descriptorStream.Write(dmi, 0, 2052); + else if(dmi.Length == 2048) + { + descriptorStream.Write(new byte[] {0x08, 0x02, 0x00, 0x00}, 0, 4); + descriptorStream.Write(dmi, 0, 2048); + } + } + + // TODO: Create fake PFI if none present + if(pfi != null) + { + descriptorStream.Seek(header.structuresOffset + 2052, SeekOrigin.Begin); + descriptorStream.Write(pfi, pfi.Length - 2048, 2048); + } + } + + // Write sessions + descriptorStream.Seek(header.sessionOffset, SeekOrigin.Begin); + foreach(AlcoholSession session in alcSessions.Values) + { + block = new byte[Marshal.SizeOf(session)]; + blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(session)); + Marshal.StructureToPtr(session, blockPtr, true); + Marshal.Copy(blockPtr, block, 0, block.Length); + Marshal.FreeHGlobal(blockPtr); + descriptorStream.Write(block, 0, block.Length); + } + + // Write tracks + foreach(KeyValuePair> kvp in alcToc) + { + descriptorStream.Seek(alcSessions.First(t => t.Key == kvp.Key).Value.trackOffset, SeekOrigin.Begin); + foreach(AlcoholTrack track in kvp.Value.Values) + { + block = new byte[Marshal.SizeOf(track)]; + blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(track)); + Marshal.StructureToPtr(track, blockPtr, true); + Marshal.Copy(blockPtr, block, 0, block.Length); + Marshal.FreeHGlobal(blockPtr); + descriptorStream.Write(block, 0, block.Length); + + if(isDvd) continue; + + // Write extra + long position = descriptorStream.Position; + descriptorStream.Seek(track.extraOffset, SeekOrigin.Begin); + if(alcTrackExtras.TryGetValue(track.point, out AlcoholTrackExtra extra)) + { + block = new byte[Marshal.SizeOf(extra)]; + blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(extra)); + Marshal.StructureToPtr(extra, blockPtr, true); + Marshal.Copy(blockPtr, block, 0, block.Length); + Marshal.FreeHGlobal(blockPtr); + descriptorStream.Write(block, 0, block.Length); + } + + descriptorStream.Seek(position, SeekOrigin.Begin); + } + } + + // Write BCA + if(bca != null) + { + descriptorStream.Seek(header.bcaOffset, SeekOrigin.Begin); + descriptorStream.Write(bca, 0, bca.Length); + } + + // Write footer + descriptorStream.Seek(footerOffset, SeekOrigin.Begin); + block = new byte[Marshal.SizeOf(alcFooter)]; + blockPtr = Marshal.AllocHGlobal(Marshal.SizeOf(alcFooter)); + Marshal.StructureToPtr(alcFooter, blockPtr, true); + Marshal.Copy(blockPtr, block, 0, block.Length); + Marshal.FreeHGlobal(blockPtr); + descriptorStream.Write(block, 0, block.Length); + + // Write filename + descriptorStream.Write(filename, 0, filename.Length); + // Write filename null termination + descriptorStream.Write(new byte[] {0, 0}, 0, 2); + + descriptorStream.Flush(); + descriptorStream.Close(); + imageStream.Flush(); + imageStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + { + if(data.Length != 1) + { + ErrorMessage = "Incorrect data size for track flags"; + return false; + } + + trackFlags.Add((byte)track.TrackSequence, data[0]); + + return true; + } + case SectorTagType.CdSectorSubchannel: + { + if(track.TrackSubchannelType == 0) + { + ErrorMessage = + $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; + return false; + } + + if(data.Length != 96) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + imageStream + .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, + SeekOrigin.Begin); + imageStream.Write(data, 0, data.Length); + + return true; + } + default: + ErrorMessage = $"Unsupported tag type {tag}"; + return false; + } + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: return WriteSectorTag(data, sectorAddress, tag); + case SectorTagType.CdSectorSubchannel: + { + if(track.TrackSubchannelType == 0) + { + ErrorMessage = + $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; + return false; + } + + if(data.Length % 96 != 0) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + for(uint i = 0; i < length; i++) + { + imageStream + .Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, + SeekOrigin.Begin); + imageStream.Write(data, (int)(i * 96), 96); + } + + return true; + } + default: + ErrorMessage = $"Unsupported tag type {tag}"; + return false; + } + } + + public bool SetDumpHardware(List dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86.cs b/DiscImageChef.DiscImages/Anex86.cs deleted file mode 100644 index 78a85a7b6..000000000 --- a/DiscImageChef.DiscImages/Anex86.cs +++ /dev/null @@ -1,551 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : Anex86.cs -// Author(s) : Natalia Portillo -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Anex86 disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see . -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class Anex86 : IWritableImage - { - IFilter anexImageFilter; - Anex86Header fdihdr; - ImageInfo imageInfo; - FileStream writingStream; - - public Anex86() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Anex86 Disk Image"; - public Guid Id => new Guid("0410003E-6E7B-40E6-9328-BA5651ADF6B7"); - - public string Format => "Anex86 disk image"; - - public List Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - fdihdr = new Anex86Header(); - - if(stream.Length < Marshal.SizeOf(fdihdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(fdihdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - fdihdr = (Anex86Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Anex86Header)); - handle.Free(); - - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.unknown = {0}", fdihdr.unknown); - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.hddtype = {0}", fdihdr.hddtype); - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.hdrSize = {0}", fdihdr.hdrSize); - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.dskSize = {0}", fdihdr.dskSize); - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.bps = {0}", fdihdr.bps); - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.spt = {0}", fdihdr.spt); - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.heads = {0}", fdihdr.heads); - DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.cylinders = {0}", fdihdr.cylinders); - - return stream.Length == fdihdr.hdrSize + fdihdr.dskSize && - fdihdr.dskSize == fdihdr.bps * fdihdr.spt * fdihdr.heads * fdihdr.cylinders; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - fdihdr = new Anex86Header(); - - if(stream.Length < Marshal.SizeOf(fdihdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(fdihdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - fdihdr = (Anex86Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Anex86Header)); - handle.Free(); - - imageInfo.MediaType = Geometry.GetMediaType(((ushort)fdihdr.cylinders, (byte)fdihdr.heads, - (ushort)fdihdr.spt, (uint)fdihdr.bps, MediaEncoding.MFM, - false)); - if(imageInfo.MediaType == MediaType.Unknown) imageInfo.MediaType = MediaType.GENERIC_HDD; - - DicConsole.DebugWriteLine("Anex86 plugin", "MediaType: {0}", imageInfo.MediaType); - - imageInfo.ImageSize = (ulong)fdihdr.dskSize; - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = (ulong)(fdihdr.cylinders * fdihdr.heads * fdihdr.spt); - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.SectorSize = (uint)fdihdr.bps; - imageInfo.Cylinders = (uint)fdihdr.cylinders; - imageInfo.Heads = (uint)fdihdr.heads; - imageInfo.SectorsPerTrack = (uint)fdihdr.spt; - - anexImageFilter = imageFilter; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Stream stream = anexImageFilter.GetDataForkStream(); - - stream.Seek((long)((ulong)fdihdr.hdrSize + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - - return buffer; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, - out List unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable SupportedSectorTags => new SectorTagType[] { }; - // TODO: Test with real hardware to see real supported media - public IEnumerable SupportedMediaTypes => - new[] - { - MediaType.IBM23FD, MediaType.ECMA_66, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, - MediaType.ACORN_525_SS_SD_40, MediaType.ACORN_525_SS_DD_40, MediaType.ATARI_525_SD, - MediaType.ATARI_525_DD, MediaType.ATARI_525_ED, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, - MediaType.ECMA_70, MediaType.Apricot_35, MediaType.RX01, MediaType.RX02, MediaType.NEC_525_HD, - MediaType.ECMA_99_15, MediaType.NEC_8_SD, MediaType.RX03, MediaType.DOS_35_SS_DD_8, - MediaType.DOS_35_SS_DD_9, MediaType.ACORN_525_SS_SD_80, MediaType.RX50, MediaType.ATARI_35_SS_DD_11, - MediaType.ACORN_525_SS_DD_80, MediaType.ACORN_35_DS_DD, MediaType.DOS_35_DS_DD_8, - MediaType.DOS_35_DS_DD_9, MediaType.ACORN_35_DS_HD, MediaType.DOS_525_HD, MediaType.ACORN_525_DS_DD, - MediaType.DOS_35_HD, MediaType.XDF_525, MediaType.DMF, MediaType.XDF_35, MediaType.DOS_35_ED, - MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_525_HD, MediaType.FDFORMAT_35_HD, MediaType.NEC_35_TD, - MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable KnownExtensions => new[] {".fdi", ".hdi"}; - - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, - uint sectorSize) - { - if(sectorSize == 0) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors * sectorSize > int.MaxValue || sectors > (long)int.MaxValue * 8 * 33) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - fdihdr = new Anex86Header {hdrSize = 4096, dskSize = (int)(sectors * sectorSize), bps = (int)sectorSize}; - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(4096 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(4096 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if((imageInfo.MediaType == MediaType.Unknown || imageInfo.MediaType == MediaType.GENERIC_HDD || - imageInfo.MediaType == MediaType.FlashDrive || imageInfo.MediaType == MediaType.CompactFlash || - imageInfo.MediaType == MediaType.CompactFlashType2 || imageInfo.MediaType == MediaType.PCCardTypeI || - imageInfo.MediaType == MediaType.PCCardTypeII || imageInfo.MediaType == MediaType.PCCardTypeIII || - imageInfo.MediaType == MediaType.PCCardTypeIV) && fdihdr.cylinders == 0) - { - fdihdr.cylinders = (int)(imageInfo.Sectors / 8 / 33); - fdihdr.heads = 8; - fdihdr.spt = 33; - - while(fdihdr.cylinders == 0) - { - fdihdr.heads--; - - if(fdihdr.heads == 0) - { - fdihdr.spt--; - fdihdr.heads = 8; - } - - fdihdr.cylinders = (int)imageInfo.Sectors / fdihdr.heads / fdihdr.spt; - - if(fdihdr.cylinders == 0 && fdihdr.heads == 0 && fdihdr.spt == 0) break; - } - } - - byte[] hdr = new byte[Marshal.SizeOf(fdihdr)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(fdihdr)); - Marshal.StructureToPtr(fdihdr, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > int.MaxValue) - { - ErrorMessage = "Too many cylinders."; - return false; - } - - if(heads > int.MaxValue) - { - ErrorMessage = "Too many heads."; - return false; - } - - if(sectorsPerTrack > int.MaxValue) - { - ErrorMessage = "Too many sectors per track."; - return false; - } - - fdihdr.spt = (int)sectorsPerTrack; - fdihdr.heads = (int)heads; - fdihdr.cylinders = (int)cylinders; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct Anex86Header - { - public int unknown; - public int hddtype; - public int hdrSize; - public int dskSize; - public int bps; - public int spt; - public int heads; - public int cylinders; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86/Anex86.cs b/DiscImageChef.DiscImages/Anex86/Anex86.cs new file mode 100644 index 000000000..33a25b6c0 --- /dev/null +++ b/DiscImageChef.DiscImages/Anex86/Anex86.cs @@ -0,0 +1,75 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Anex86.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Anex86 : IWritableImage + { + IFilter anexImageFilter; + Anex86Header fdihdr; + ImageInfo imageInfo; + FileStream writingStream; + + public Anex86() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86/Identify.cs b/DiscImageChef.DiscImages/Anex86/Identify.cs new file mode 100644 index 000000000..24b992fb0 --- /dev/null +++ b/DiscImageChef.DiscImages/Anex86/Identify.cs @@ -0,0 +1,71 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Anex86 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + fdihdr = new Anex86Header(); + + if(stream.Length < Marshal.SizeOf(fdihdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(fdihdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + fdihdr = (Anex86Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Anex86Header)); + handle.Free(); + + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.unknown = {0}", fdihdr.unknown); + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.hddtype = {0}", fdihdr.hddtype); + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.hdrSize = {0}", fdihdr.hdrSize); + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.dskSize = {0}", fdihdr.dskSize); + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.bps = {0}", fdihdr.bps); + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.spt = {0}", fdihdr.spt); + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.heads = {0}", fdihdr.heads); + DicConsole.DebugWriteLine("Anex86 plugin", "fdihdr.cylinders = {0}", fdihdr.cylinders); + + return stream.Length == fdihdr.hdrSize + fdihdr.dskSize && + fdihdr.dskSize == fdihdr.bps * fdihdr.spt * fdihdr.heads * fdihdr.cylinders; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86/Properties.cs b/DiscImageChef.DiscImages/Anex86/Properties.cs new file mode 100644 index 000000000..0506bd6c8 --- /dev/null +++ b/DiscImageChef.DiscImages/Anex86/Properties.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Anex86 + { + public ImageInfo Info => imageInfo; + + public string Name => "Anex86 Disk Image"; + public Guid Id => new Guid("0410003E-6E7B-40E6-9328-BA5651ADF6B7"); + + public string Format => "Anex86 disk image"; + + public List Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + + public IEnumerable SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable SupportedSectorTags => new SectorTagType[] { }; + // TODO: Test with real hardware to see real supported media + public IEnumerable SupportedMediaTypes => + new[] + { + MediaType.IBM23FD, MediaType.ECMA_66, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, + MediaType.ACORN_525_SS_SD_40, MediaType.ACORN_525_SS_DD_40, MediaType.ATARI_525_SD, + MediaType.ATARI_525_DD, MediaType.ATARI_525_ED, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, + MediaType.ECMA_70, MediaType.Apricot_35, MediaType.RX01, MediaType.RX02, MediaType.NEC_525_HD, + MediaType.ECMA_99_15, MediaType.NEC_8_SD, MediaType.RX03, MediaType.DOS_35_SS_DD_8, + MediaType.DOS_35_SS_DD_9, MediaType.ACORN_525_SS_SD_80, MediaType.RX50, MediaType.ATARI_35_SS_DD_11, + MediaType.ACORN_525_SS_DD_80, MediaType.ACORN_35_DS_DD, MediaType.DOS_35_DS_DD_8, + MediaType.DOS_35_DS_DD_9, MediaType.ACORN_35_DS_HD, MediaType.DOS_525_HD, MediaType.ACORN_525_DS_DD, + MediaType.DOS_35_HD, MediaType.XDF_525, MediaType.DMF, MediaType.XDF_35, MediaType.DOS_35_ED, + MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_525_HD, MediaType.FDFORMAT_35_HD, MediaType.NEC_35_TD, + MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable KnownExtensions => new[] {".fdi", ".hdi"}; + + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86/Read.cs b/DiscImageChef.DiscImages/Anex86/Read.cs new file mode 100644 index 000000000..b5c5cd51b --- /dev/null +++ b/DiscImageChef.DiscImages/Anex86/Read.cs @@ -0,0 +1,129 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Anex86 + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + fdihdr = new Anex86Header(); + + if(stream.Length < Marshal.SizeOf(fdihdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(fdihdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + fdihdr = (Anex86Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Anex86Header)); + handle.Free(); + + imageInfo.MediaType = Geometry.GetMediaType(((ushort)fdihdr.cylinders, (byte)fdihdr.heads, + (ushort)fdihdr.spt, (uint)fdihdr.bps, MediaEncoding.MFM, + false)); + if(imageInfo.MediaType == MediaType.Unknown) imageInfo.MediaType = MediaType.GENERIC_HDD; + + DicConsole.DebugWriteLine("Anex86 plugin", "MediaType: {0}", imageInfo.MediaType); + + imageInfo.ImageSize = (ulong)fdihdr.dskSize; + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = (ulong)(fdihdr.cylinders * fdihdr.heads * fdihdr.spt); + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.SectorSize = (uint)fdihdr.bps; + imageInfo.Cylinders = (uint)fdihdr.cylinders; + imageInfo.Heads = (uint)fdihdr.heads; + imageInfo.SectorsPerTrack = (uint)fdihdr.spt; + + anexImageFilter = imageFilter; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Stream stream = anexImageFilter.GetDataForkStream(); + + stream.Seek((long)((ulong)fdihdr.hdrSize + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + + return buffer; + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86/Structs.cs b/DiscImageChef.DiscImages/Anex86/Structs.cs new file mode 100644 index 000000000..8c2228e4c --- /dev/null +++ b/DiscImageChef.DiscImages/Anex86/Structs.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Anex86 + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Anex86Header + { + public int unknown; + public int hddtype; + public int hdrSize; + public int dskSize; + public int bps; + public int spt; + public int heads; + public int cylinders; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86/Unsupported.cs b/DiscImageChef.DiscImages/Anex86/Unsupported.cs new file mode 100644 index 000000000..9e3424704 --- /dev/null +++ b/DiscImageChef.DiscImages/Anex86/Unsupported.cs @@ -0,0 +1,119 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Anex86 : IWritableImage + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, + out List unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Anex86/Write.cs b/DiscImageChef.DiscImages/Anex86/Write.cs new file mode 100644 index 000000000..7983b2317 --- /dev/null +++ b/DiscImageChef.DiscImages/Anex86/Write.cs @@ -0,0 +1,269 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Anex86 + { + public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, + uint sectorSize) + { + if(sectorSize == 0) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors * sectorSize > int.MaxValue || sectors > (long)int.MaxValue * 8 * 33) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + fdihdr = new Anex86Header {hdrSize = 4096, dskSize = (int)(sectors * sectorSize), bps = (int)sectorSize}; + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(4096 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(4096 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if((imageInfo.MediaType == MediaType.Unknown || imageInfo.MediaType == MediaType.GENERIC_HDD || + imageInfo.MediaType == MediaType.FlashDrive || imageInfo.MediaType == MediaType.CompactFlash || + imageInfo.MediaType == MediaType.CompactFlashType2 || imageInfo.MediaType == MediaType.PCCardTypeI || + imageInfo.MediaType == MediaType.PCCardTypeII || imageInfo.MediaType == MediaType.PCCardTypeIII || + imageInfo.MediaType == MediaType.PCCardTypeIV) && fdihdr.cylinders == 0) + { + fdihdr.cylinders = (int)(imageInfo.Sectors / 8 / 33); + fdihdr.heads = 8; + fdihdr.spt = 33; + + while(fdihdr.cylinders == 0) + { + fdihdr.heads--; + + if(fdihdr.heads == 0) + { + fdihdr.spt--; + fdihdr.heads = 8; + } + + fdihdr.cylinders = (int)imageInfo.Sectors / fdihdr.heads / fdihdr.spt; + + if(fdihdr.cylinders == 0 && fdihdr.heads == 0 && fdihdr.spt == 0) break; + } + } + + byte[] hdr = new byte[Marshal.SizeOf(fdihdr)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(fdihdr)); + Marshal.StructureToPtr(fdihdr, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > int.MaxValue) + { + ErrorMessage = "Too many cylinders."; + return false; + } + + if(heads > int.MaxValue) + { + ErrorMessage = "Too many heads."; + return false; + } + + if(sectorsPerTrack > int.MaxValue) + { + ErrorMessage = "Too many sectors per track."; + return false; + } + + fdihdr.spt = (int)sectorsPerTrack; + fdihdr.heads = (int)heads; + fdihdr.cylinders = (int)cylinders; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG.cs b/DiscImageChef.DiscImages/Apple2MG.cs deleted file mode 100644 index 1c00ce193..000000000 --- a/DiscImageChef.DiscImages/Apple2MG.cs +++ /dev/null @@ -1,852 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : Apple2MG.cs -// Author(s) : Natalia Portillo -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages XGS emulator disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see . -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class Apple2Mg : IWritableImage - { - /// - /// Magic number, "2IMG" - /// - const uint MAGIC = 0x474D4932; - /// - /// Disk image created by ASIMOV2, "!nfc" - /// - const uint CREATOR_ASIMOV = 0x63666E21; - /// - /// Disk image created by Bernie ][ the Rescue, "B2TR" - /// - const uint CREATOR_BERNIE = 0x52543242; - /// - /// Disk image created by Catakig, "CTKG" - /// - const uint CREATOR_CATAKIG = 0x474B5443; - /// - /// Disk image created by Sheppy's ImageMaker, "ShIm" - /// - const uint CREATOR_SHEPPY = 0x6D496853; - /// - /// Disk image created by Sweet16, "WOOF" - /// - const uint CREATOR_SWEET = 0x464F4F57; - /// - /// Disk image created by XGS, "XGS!" - /// - const uint CREATOR_XGS = 0x21534758; - /// - /// Disk image created by CiderPress, "CdrP" - /// - const uint CREATOR_CIDER = 0x50726443; - /// - /// Disk image created by DiscImageChef, "dic " - /// - const uint CREATOR_DIC = 0x20636964; - - const uint LOCKED_DISK = 0x80000000; - const uint VALID_VOLUME_NUMBER = 0x00000100; - const uint VOLUME_NUMBER_MASK = 0x000000FF; - readonly int[] deinterleave = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - - readonly int[] interleave = {0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15}; - - IFilter a2MgImageFilter; - byte[] decodedImage; - A2ImgHeader imageHeader; - ImageInfo imageInfo; - FileStream writingStream; - - public Apple2Mg() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Apple 2IMG"; - public Guid Id => new Guid("CBAF8824-BA5F-415F-953A-19A03519B2D1"); - - public string Format => "Apple 2IMG"; - - public List Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 65) return false; - - byte[] header = new byte[64]; - stream.Read(header, 0, 64); - - uint magic = BitConverter.ToUInt32(header, 0x00); - if(magic != MAGIC) return false; - - uint dataoff = BitConverter.ToUInt32(header, 0x18); - if(dataoff > stream.Length) return false; - - uint datasize = BitConverter.ToUInt32(header, 0x1C); - // There seems to be incorrect endian in some images on the wild - if(datasize == 0x00800C00) datasize = 0x000C8000; - if(dataoff + datasize > stream.Length) return false; - - uint commentoff = BitConverter.ToUInt32(header, 0x20); - if(commentoff > stream.Length) return false; - - uint commentsize = BitConverter.ToUInt32(header, 0x24); - if(commentoff + commentsize > stream.Length) return false; - - uint creatoroff = BitConverter.ToUInt32(header, 0x28); - if(creatoroff > stream.Length) return false; - - uint creatorsize = BitConverter.ToUInt32(header, 0x2C); - return creatoroff + creatorsize <= stream.Length; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - imageHeader = new A2ImgHeader(); - - byte[] header = new byte[64]; - stream.Read(header, 0, 64); - byte[] magic = new byte[4]; - byte[] creator = new byte[4]; - - Array.Copy(header, 0, magic, 0, 4); - Array.Copy(header, 4, creator, 0, 4); - - imageHeader.Magic = BitConverter.ToUInt32(header, 0x00); - imageHeader.Creator = BitConverter.ToUInt32(header, 0x04); - imageHeader.HeaderSize = BitConverter.ToUInt16(header, 0x08); - imageHeader.Version = BitConverter.ToUInt16(header, 0x0A); - imageHeader.ImageFormat = (SectorOrder)BitConverter.ToUInt32(header, 0x0C); - imageHeader.Flags = BitConverter.ToUInt32(header, 0x10); - imageHeader.Blocks = BitConverter.ToUInt32(header, 0x14); - imageHeader.DataOffset = BitConverter.ToUInt32(header, 0x18); - imageHeader.DataSize = BitConverter.ToUInt32(header, 0x1C); - imageHeader.CommentOffset = BitConverter.ToUInt32(header, 0x20); - imageHeader.CommentSize = BitConverter.ToUInt32(header, 0x24); - imageHeader.CreatorSpecificOffset = BitConverter.ToUInt32(header, 0x28); - imageHeader.CreatorSpecificSize = BitConverter.ToUInt32(header, 0x2C); - imageHeader.Reserved1 = BitConverter.ToUInt32(header, 0x30); - imageHeader.Reserved2 = BitConverter.ToUInt32(header, 0x34); - imageHeader.Reserved3 = BitConverter.ToUInt32(header, 0x38); - imageHeader.Reserved4 = BitConverter.ToUInt32(header, 0x3C); - - if(imageHeader.DataSize == 0x00800C00) - { - imageHeader.DataSize = 0x000C8000; - DicConsole.DebugWriteLine("2MG plugin", "Detected incorrect endian on data size field, correcting."); - } - - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.magic = \"{0}\"", - Encoding.ASCII.GetString(magic)); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.creator = \"{0}\"", - Encoding.ASCII.GetString(creator)); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.headerSize = {0}", imageHeader.HeaderSize); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.version = {0}", imageHeader.Version); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.imageFormat = {0}", imageHeader.ImageFormat); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.flags = 0x{0:X8}", imageHeader.Flags); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.blocks = {0}", imageHeader.Blocks); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.dataOffset = 0x{0:X8}", imageHeader.DataOffset); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.dataSize = {0}", imageHeader.DataSize); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.commentOffset = 0x{0:X8}", imageHeader.CommentOffset); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.commentSize = {0}", imageHeader.CommentSize); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.creatorSpecificOffset = 0x{0:X8}", - imageHeader.CreatorSpecificOffset); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.creatorSpecificSize = {0}", - imageHeader.CreatorSpecificSize); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved1 = 0x{0:X8}", imageHeader.Reserved1); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved2 = 0x{0:X8}", imageHeader.Reserved2); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved3 = 0x{0:X8}", imageHeader.Reserved3); - DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved4 = 0x{0:X8}", imageHeader.Reserved4); - - if(imageHeader.DataSize == 0 && imageHeader.Blocks == 0 && - imageHeader.ImageFormat != SectorOrder.ProDos) return false; - - byte[] tmp; - int[] offsets; - - switch(imageHeader.ImageFormat) - { - case SectorOrder.Nibbles: - tmp = new byte[imageHeader.DataSize]; - stream.Seek(imageHeader.DataOffset, SeekOrigin.Begin); - stream.Read(tmp, 0, tmp.Length); - AppleNib nibPlugin = new AppleNib(); - ZZZNoFilter noFilter = new ZZZNoFilter(); - noFilter.Open(tmp); - nibPlugin.Open(noFilter); - decodedImage = nibPlugin.ReadSectors(0, (uint)nibPlugin.Info.Sectors); - imageInfo.Sectors = nibPlugin.Info.Sectors; - imageInfo.SectorSize = nibPlugin.Info.SectorSize; - break; - case SectorOrder.Dos when imageHeader.DataSize == 143360: - case SectorOrder.ProDos when imageHeader.DataSize == 143360: - stream.Seek(imageHeader.DataOffset, SeekOrigin.Begin); - tmp = new byte[imageHeader.DataSize]; - stream.Read(tmp, 0, tmp.Length); - bool isDos = tmp[0x11001] == 17 && tmp[0x11002] < 16 && tmp[0x11027] <= 122 && tmp[0x11034] == 35 && - tmp[0x11035] == 16 && tmp[0x11036] == 0 && tmp[0x11037] == 1; - decodedImage = new byte[imageHeader.DataSize]; - offsets = imageHeader.ImageFormat == SectorOrder.Dos - ? (isDos ? deinterleave : interleave) - : (isDos ? interleave : deinterleave); - for(int t = 0; t < 35; t++) - { - for(int s = 0; s < 16; s++) - Array.Copy(tmp, t * 16 * 256 + s * 256, decodedImage, t * 16 * 256 + offsets[s] * 256, 256); - } - - imageInfo.Sectors = 560; - imageInfo.SectorSize = 256; - break; - case SectorOrder.Dos when imageHeader.DataSize == 819200: - stream.Seek(imageHeader.DataOffset, SeekOrigin.Begin); - tmp = new byte[imageHeader.DataSize]; - stream.Read(tmp, 0, tmp.Length); - decodedImage = new byte[imageHeader.DataSize]; - offsets = interleave; - for(int t = 0; t < 200; t++) - { - for(int s = 0; s < 16; s++) - Array.Copy(tmp, t * 16 * 256 + s * 256, decodedImage, t * 16 * 256 + offsets[s] * 256, 256); - } - - imageInfo.Sectors = 1600; - imageInfo.SectorSize = 512; - break; - default: - decodedImage = null; - imageInfo.SectorSize = 512; - imageInfo.Sectors = imageHeader.DataSize / 512; - break; - } - - imageInfo.ImageSize = imageHeader.DataSize; - - switch(imageHeader.Creator) - { - case CREATOR_ASIMOV: - imageInfo.Application = "ASIMOV2"; - break; - case CREATOR_BERNIE: - imageInfo.Application = "Bernie ][ the Rescue"; - break; - case CREATOR_CATAKIG: - imageInfo.Application = "Catakig"; - break; - case CREATOR_SHEPPY: - imageInfo.Application = "Sheppy's ImageMaker"; - break; - case CREATOR_SWEET: - imageInfo.Application = "Sweet16"; - break; - case CREATOR_XGS: - imageInfo.Application = "XGS"; - break; - case CREATOR_CIDER: - imageInfo.Application = "CiderPress"; - break; - case CREATOR_DIC: - imageInfo.Application = "DiscImageChef"; - break; - default: - imageInfo.Application = $"Unknown creator code \"{Encoding.ASCII.GetString(creator)}\""; - break; - } - - imageInfo.Version = imageHeader.Version.ToString(); - - if(imageHeader.CommentOffset != 0 && imageHeader.CommentSize != 0) - { - stream.Seek(imageHeader.CommentOffset, SeekOrigin.Begin); - - byte[] comments = new byte[imageHeader.CommentSize]; - stream.Read(comments, 0, (int)imageHeader.CommentSize); - imageInfo.Comments = Encoding.ASCII.GetString(comments); - } - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.MediaType = GetMediaType(); - - a2MgImageFilter = imageFilter; - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - - DicConsole.VerboseWriteLine("2MG image contains a disk of type {0}", imageInfo.MediaType); - if(!string.IsNullOrEmpty(imageInfo.Comments)) - DicConsole.VerboseWriteLine("2MG comments: {0}", imageInfo.Comments); - - switch(imageInfo.MediaType) - { - case MediaType.Apple32SS: - imageInfo.Cylinders = 35; - imageInfo.Heads = 1; - imageInfo.SectorsPerTrack = 13; - break; - case MediaType.Apple32DS: - imageInfo.Cylinders = 35; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 13; - break; - case MediaType.Apple33SS: - imageInfo.Cylinders = 35; - imageInfo.Heads = 1; - imageInfo.SectorsPerTrack = 16; - break; - case MediaType.Apple33DS: - imageInfo.Cylinders = 35; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 16; - break; - case MediaType.AppleSonySS: - imageInfo.Cylinders = 80; - imageInfo.Heads = 1; - // Variable sectors per track, this suffices - imageInfo.SectorsPerTrack = 10; - break; - case MediaType.AppleSonyDS: - imageInfo.Cylinders = 80; - imageInfo.Heads = 2; - // Variable sectors per track, this suffices - imageInfo.SectorsPerTrack = 10; - break; - case MediaType.DOS_35_HD: - imageInfo.Cylinders = 80; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 18; - break; - } - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - if(decodedImage != null) - Array.Copy(decodedImage, (long)(sectorAddress * imageInfo.SectorSize), buffer, 0, - length * imageInfo.SectorSize); - else - { - Stream stream = a2MgImageFilter.GetDataForkStream(); - stream.Seek((long)(imageHeader.DataOffset + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - } - - return buffer; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, - out List unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable SupportedMediaTypes => - new[] - { - MediaType.Apple32SS, MediaType.Apple33SS, MediaType.AppleSonySS, MediaType.AppleSonyDS, - MediaType.DOS_35_HD, MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, - MediaType.CompactFlash, MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, - MediaType.PCCardTypeIII, MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable KnownExtensions => new[] {".2mg"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - if(sectorSize != 256 || mediaType != MediaType.Apple32SS && mediaType != MediaType.Apple33SS) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - if(sectors > uint.MaxValue) - { - ErrorMessage = "Too many sectors"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(0x40 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(0x40 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - writingStream.Seek(0x40 + 17 * 16 * 256, SeekOrigin.Begin); - byte[] tmp = new byte[256]; - writingStream.Read(tmp, 0, tmp.Length); - bool isDos = tmp[0x01] == 17 && tmp[0x02] < 16 && tmp[0x27] <= 122 && tmp[0x34] == 35 && tmp[0x35] == 16 && - tmp[0x36] == 0 && tmp[0x37] == 1; - - imageHeader = new A2ImgHeader - { - Blocks = (uint)(imageInfo.Sectors * imageInfo.SectorSize) / 512, - Creator = CREATOR_DIC, - DataOffset = 0x40, - DataSize = (uint)(imageInfo.Sectors * imageInfo.SectorSize), - Flags = - (uint)(imageInfo.LastMediaSequence != 0 - ? VALID_VOLUME_NUMBER + (imageInfo.MediaSequence & 0xFF) - : 0), - HeaderSize = 0x40, - ImageFormat = isDos ? SectorOrder.Dos : SectorOrder.ProDos, - Magic = MAGIC, - Version = 1 - }; - - if(!string.IsNullOrEmpty(imageInfo.Comments)) - { - writingStream.Seek(0, SeekOrigin.End); - tmp = Encoding.UTF8.GetBytes(imageInfo.Comments); - imageHeader.CommentOffset = (uint)writingStream.Position; - imageHeader.CommentSize = (uint)(tmp.Length + 1); - writingStream.Write(tmp, 0, tmp.Length); - writingStream.WriteByte(0); - } - - byte[] hdr = new byte[Marshal.SizeOf(typeof(A2ImgHeader))]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(A2ImgHeader))); - Marshal.StructureToPtr(imageHeader, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.Comments = metadata.Comments; - imageInfo.LastMediaSequence = metadata.LastMediaSequence; - imageInfo.MediaSequence = metadata.MediaSequence; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetDumpHardware(List dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - MediaType GetMediaType() - { - switch(imageInfo.Sectors) - { - case 455: return MediaType.Apple32SS; - case 910: return MediaType.Apple32DS; - case 560: return MediaType.Apple33SS; - case 1120: return MediaType.Apple33DS; - case 800: return MediaType.AppleSonySS; - case 1600: return MediaType.AppleSonyDS; - case 2880: return MediaType.DOS_35_HD; - default: return MediaType.Unknown; - } - } - - enum SectorOrder : uint - { - Dos = 0, - ProDos = 1, - Nibbles = 2 - } - - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct A2ImgHeader - { - /// - /// Offset 0x00, magic - /// - public uint Magic; - /// - /// Offset 0x04, disk image creator ID - /// - public uint Creator; - /// - /// Offset 0x08, header size, constant 0x0040 - /// - public ushort HeaderSize; - /// - /// Offset 0x0A, disk image version - /// - public ushort Version; - /// - /// Offset 0x0C, disk image format - /// - public SectorOrder ImageFormat; - /// - /// Offset 0x10, flags and volume number - /// - public uint Flags; - /// - /// Offset 0x14, blocks for ProDOS, 0 otherwise - /// - public uint Blocks; - /// - /// Offset 0x18, offset to data - /// - public uint DataOffset; - /// - /// Offset 0x1C, data size in bytes - /// - public uint DataSize; - /// - /// Offset 0x20, offset to optional comment - /// - public uint CommentOffset; - /// - /// Offset 0x24, length of optional comment - /// - public uint CommentSize; - /// - /// Offset 0x28, offset to creator specific chunk - /// - public uint CreatorSpecificOffset; - /// - /// Offset 0x2C, creator specific chunk size - /// - public uint CreatorSpecificSize; - /// - /// Offset 0x30, reserved, should be zero - /// - public uint Reserved1; - /// - /// Offset 0x34, reserved, should be zero - /// - public uint Reserved2; - /// - /// Offset 0x38, reserved, should be zero - /// - public uint Reserved3; - /// - /// Offset 0x3C, reserved, should be zero - /// - public uint Reserved4; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Apple2MG.cs b/DiscImageChef.DiscImages/Apple2MG/Apple2MG.cs new file mode 100644 index 000000000..160ab9d71 --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Apple2MG.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Apple2MG.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg : IWritableImage + { + IFilter a2MgImageFilter; + byte[] decodedImage; + A2ImgHeader imageHeader; + ImageInfo imageInfo; + FileStream writingStream; + + public Apple2Mg() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Constants.cs b/DiscImageChef.DiscImages/Apple2MG/Constants.cs new file mode 100644 index 000000000..05138921a --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Constants.cs @@ -0,0 +1,80 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + /// + /// Magic number, "2IMG" + /// + const uint MAGIC = 0x474D4932; + /// + /// Disk image created by ASIMOV2, "!nfc" + /// + const uint CREATOR_ASIMOV = 0x63666E21; + /// + /// Disk image created by Bernie ][ the Rescue, "B2TR" + /// + const uint CREATOR_BERNIE = 0x52543242; + /// + /// Disk image created by Catakig, "CTKG" + /// + const uint CREATOR_CATAKIG = 0x474B5443; + /// + /// Disk image created by Sheppy's ImageMaker, "ShIm" + /// + const uint CREATOR_SHEPPY = 0x6D496853; + /// + /// Disk image created by Sweet16, "WOOF" + /// + const uint CREATOR_SWEET = 0x464F4F57; + /// + /// Disk image created by XGS, "XGS!" + /// + const uint CREATOR_XGS = 0x21534758; + /// + /// Disk image created by CiderPress, "CdrP" + /// + const uint CREATOR_CIDER = 0x50726443; + /// + /// Disk image created by DiscImageChef, "dic " + /// + const uint CREATOR_DIC = 0x20636964; + + const uint LOCKED_DISK = 0x80000000; + const uint VALID_VOLUME_NUMBER = 0x00000100; + const uint VOLUME_NUMBER_MASK = 0x000000FF; + readonly int[] deinterleave = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + readonly int[] interleave = {0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Enums.cs b/DiscImageChef.DiscImages/Apple2MG/Enums.cs new file mode 100644 index 000000000..ce395943a --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Enums.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + enum SectorOrder : uint + { + Dos = 0, + ProDos = 1, + Nibbles = 2 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Helpers.cs b/DiscImageChef.DiscImages/Apple2MG/Helpers.cs new file mode 100644 index 000000000..97a38f10a --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Helpers.cs @@ -0,0 +1,54 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + MediaType GetMediaType() + { + switch(imageInfo.Sectors) + { + case 455: return MediaType.Apple32SS; + case 910: return MediaType.Apple32DS; + case 560: return MediaType.Apple33SS; + case 1120: return MediaType.Apple33DS; + case 800: return MediaType.AppleSonySS; + case 1600: return MediaType.AppleSonyDS; + case 2880: return MediaType.DOS_35_HD; + default: return MediaType.Unknown; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Identify.cs b/DiscImageChef.DiscImages/Apple2MG/Identify.cs new file mode 100644 index 000000000..76455d513 --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Identify.cs @@ -0,0 +1,75 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 65) return false; + + byte[] header = new byte[64]; + stream.Read(header, 0, 64); + + uint magic = BitConverter.ToUInt32(header, 0x00); + if(magic != MAGIC) return false; + + uint dataoff = BitConverter.ToUInt32(header, 0x18); + if(dataoff > stream.Length) return false; + + uint datasize = BitConverter.ToUInt32(header, 0x1C); + // There seems to be incorrect endian in some images on the wild + if(datasize == 0x00800C00) datasize = 0x000C8000; + if(dataoff + datasize > stream.Length) return false; + + uint commentoff = BitConverter.ToUInt32(header, 0x20); + if(commentoff > stream.Length) return false; + + uint commentsize = BitConverter.ToUInt32(header, 0x24); + if(commentoff + commentsize > stream.Length) return false; + + uint creatoroff = BitConverter.ToUInt32(header, 0x28); + if(creatoroff > stream.Length) return false; + + uint creatorsize = BitConverter.ToUInt32(header, 0x2C); + return creatoroff + creatorsize <= stream.Length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Properties.cs b/DiscImageChef.DiscImages/Apple2MG/Properties.cs new file mode 100644 index 000000000..6614b39e5 --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Properties.cs @@ -0,0 +1,80 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + public ImageInfo Info => imageInfo; + + public string Name => "Apple 2IMG"; + public Guid Id => new Guid("CBAF8824-BA5F-415F-953A-19A03519B2D1"); + + public string Format => "Apple 2IMG"; + + public List Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + + public IEnumerable SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable SupportedMediaTypes => + new[] + { + MediaType.Apple32SS, MediaType.Apple33SS, MediaType.AppleSonySS, MediaType.AppleSonyDS, + MediaType.DOS_35_HD, MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, + MediaType.CompactFlash, MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, + MediaType.PCCardTypeIII, MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable KnownExtensions => new[] {".2mg"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Read.cs b/DiscImageChef.DiscImages/Apple2MG/Read.cs new file mode 100644 index 000000000..6abae2272 --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Read.cs @@ -0,0 +1,298 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; +using DiscImageChef.Filters; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + imageHeader = new A2ImgHeader(); + + byte[] header = new byte[64]; + stream.Read(header, 0, 64); + byte[] magic = new byte[4]; + byte[] creator = new byte[4]; + + Array.Copy(header, 0, magic, 0, 4); + Array.Copy(header, 4, creator, 0, 4); + + imageHeader.Magic = BitConverter.ToUInt32(header, 0x00); + imageHeader.Creator = BitConverter.ToUInt32(header, 0x04); + imageHeader.HeaderSize = BitConverter.ToUInt16(header, 0x08); + imageHeader.Version = BitConverter.ToUInt16(header, 0x0A); + imageHeader.ImageFormat = (SectorOrder)BitConverter.ToUInt32(header, 0x0C); + imageHeader.Flags = BitConverter.ToUInt32(header, 0x10); + imageHeader.Blocks = BitConverter.ToUInt32(header, 0x14); + imageHeader.DataOffset = BitConverter.ToUInt32(header, 0x18); + imageHeader.DataSize = BitConverter.ToUInt32(header, 0x1C); + imageHeader.CommentOffset = BitConverter.ToUInt32(header, 0x20); + imageHeader.CommentSize = BitConverter.ToUInt32(header, 0x24); + imageHeader.CreatorSpecificOffset = BitConverter.ToUInt32(header, 0x28); + imageHeader.CreatorSpecificSize = BitConverter.ToUInt32(header, 0x2C); + imageHeader.Reserved1 = BitConverter.ToUInt32(header, 0x30); + imageHeader.Reserved2 = BitConverter.ToUInt32(header, 0x34); + imageHeader.Reserved3 = BitConverter.ToUInt32(header, 0x38); + imageHeader.Reserved4 = BitConverter.ToUInt32(header, 0x3C); + + if(imageHeader.DataSize == 0x00800C00) + { + imageHeader.DataSize = 0x000C8000; + DicConsole.DebugWriteLine("2MG plugin", "Detected incorrect endian on data size field, correcting."); + } + + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.magic = \"{0}\"", + Encoding.ASCII.GetString(magic)); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.creator = \"{0}\"", + Encoding.ASCII.GetString(creator)); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.headerSize = {0}", imageHeader.HeaderSize); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.version = {0}", imageHeader.Version); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.imageFormat = {0}", imageHeader.ImageFormat); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.flags = 0x{0:X8}", imageHeader.Flags); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.blocks = {0}", imageHeader.Blocks); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.dataOffset = 0x{0:X8}", imageHeader.DataOffset); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.dataSize = {0}", imageHeader.DataSize); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.commentOffset = 0x{0:X8}", imageHeader.CommentOffset); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.commentSize = {0}", imageHeader.CommentSize); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.creatorSpecificOffset = 0x{0:X8}", + imageHeader.CreatorSpecificOffset); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.creatorSpecificSize = {0}", + imageHeader.CreatorSpecificSize); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved1 = 0x{0:X8}", imageHeader.Reserved1); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved2 = 0x{0:X8}", imageHeader.Reserved2); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved3 = 0x{0:X8}", imageHeader.Reserved3); + DicConsole.DebugWriteLine("2MG plugin", "ImageHeader.reserved4 = 0x{0:X8}", imageHeader.Reserved4); + + if(imageHeader.DataSize == 0 && imageHeader.Blocks == 0 && + imageHeader.ImageFormat != SectorOrder.ProDos) return false; + + byte[] tmp; + int[] offsets; + + switch(imageHeader.ImageFormat) + { + case SectorOrder.Nibbles: + tmp = new byte[imageHeader.DataSize]; + stream.Seek(imageHeader.DataOffset, SeekOrigin.Begin); + stream.Read(tmp, 0, tmp.Length); + AppleNib nibPlugin = new AppleNib(); + ZZZNoFilter noFilter = new ZZZNoFilter(); + noFilter.Open(tmp); + nibPlugin.Open(noFilter); + decodedImage = nibPlugin.ReadSectors(0, (uint)nibPlugin.Info.Sectors); + imageInfo.Sectors = nibPlugin.Info.Sectors; + imageInfo.SectorSize = nibPlugin.Info.SectorSize; + break; + case SectorOrder.Dos when imageHeader.DataSize == 143360: + case SectorOrder.ProDos when imageHeader.DataSize == 143360: + stream.Seek(imageHeader.DataOffset, SeekOrigin.Begin); + tmp = new byte[imageHeader.DataSize]; + stream.Read(tmp, 0, tmp.Length); + bool isDos = tmp[0x11001] == 17 && tmp[0x11002] < 16 && tmp[0x11027] <= 122 && tmp[0x11034] == 35 && + tmp[0x11035] == 16 && tmp[0x11036] == 0 && tmp[0x11037] == 1; + decodedImage = new byte[imageHeader.DataSize]; + offsets = imageHeader.ImageFormat == SectorOrder.Dos + ? (isDos ? deinterleave : interleave) + : (isDos ? interleave : deinterleave); + for(int t = 0; t < 35; t++) + { + for(int s = 0; s < 16; s++) + Array.Copy(tmp, t * 16 * 256 + s * 256, decodedImage, t * 16 * 256 + offsets[s] * 256, 256); + } + + imageInfo.Sectors = 560; + imageInfo.SectorSize = 256; + break; + case SectorOrder.Dos when imageHeader.DataSize == 819200: + stream.Seek(imageHeader.DataOffset, SeekOrigin.Begin); + tmp = new byte[imageHeader.DataSize]; + stream.Read(tmp, 0, tmp.Length); + decodedImage = new byte[imageHeader.DataSize]; + offsets = interleave; + for(int t = 0; t < 200; t++) + { + for(int s = 0; s < 16; s++) + Array.Copy(tmp, t * 16 * 256 + s * 256, decodedImage, t * 16 * 256 + offsets[s] * 256, 256); + } + + imageInfo.Sectors = 1600; + imageInfo.SectorSize = 512; + break; + default: + decodedImage = null; + imageInfo.SectorSize = 512; + imageInfo.Sectors = imageHeader.DataSize / 512; + break; + } + + imageInfo.ImageSize = imageHeader.DataSize; + + switch(imageHeader.Creator) + { + case CREATOR_ASIMOV: + imageInfo.Application = "ASIMOV2"; + break; + case CREATOR_BERNIE: + imageInfo.Application = "Bernie ][ the Rescue"; + break; + case CREATOR_CATAKIG: + imageInfo.Application = "Catakig"; + break; + case CREATOR_SHEPPY: + imageInfo.Application = "Sheppy's ImageMaker"; + break; + case CREATOR_SWEET: + imageInfo.Application = "Sweet16"; + break; + case CREATOR_XGS: + imageInfo.Application = "XGS"; + break; + case CREATOR_CIDER: + imageInfo.Application = "CiderPress"; + break; + case CREATOR_DIC: + imageInfo.Application = "DiscImageChef"; + break; + default: + imageInfo.Application = $"Unknown creator code \"{Encoding.ASCII.GetString(creator)}\""; + break; + } + + imageInfo.Version = imageHeader.Version.ToString(); + + if(imageHeader.CommentOffset != 0 && imageHeader.CommentSize != 0) + { + stream.Seek(imageHeader.CommentOffset, SeekOrigin.Begin); + + byte[] comments = new byte[imageHeader.CommentSize]; + stream.Read(comments, 0, (int)imageHeader.CommentSize); + imageInfo.Comments = Encoding.ASCII.GetString(comments); + } + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.MediaType = GetMediaType(); + + a2MgImageFilter = imageFilter; + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + + DicConsole.VerboseWriteLine("2MG image contains a disk of type {0}", imageInfo.MediaType); + if(!string.IsNullOrEmpty(imageInfo.Comments)) + DicConsole.VerboseWriteLine("2MG comments: {0}", imageInfo.Comments); + + switch(imageInfo.MediaType) + { + case MediaType.Apple32SS: + imageInfo.Cylinders = 35; + imageInfo.Heads = 1; + imageInfo.SectorsPerTrack = 13; + break; + case MediaType.Apple32DS: + imageInfo.Cylinders = 35; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 13; + break; + case MediaType.Apple33SS: + imageInfo.Cylinders = 35; + imageInfo.Heads = 1; + imageInfo.SectorsPerTrack = 16; + break; + case MediaType.Apple33DS: + imageInfo.Cylinders = 35; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 16; + break; + case MediaType.AppleSonySS: + imageInfo.Cylinders = 80; + imageInfo.Heads = 1; + // Variable sectors per track, this suffices + imageInfo.SectorsPerTrack = 10; + break; + case MediaType.AppleSonyDS: + imageInfo.Cylinders = 80; + imageInfo.Heads = 2; + // Variable sectors per track, this suffices + imageInfo.SectorsPerTrack = 10; + break; + case MediaType.DOS_35_HD: + imageInfo.Cylinders = 80; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 18; + break; + } + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + if(decodedImage != null) + Array.Copy(decodedImage, (long)(sectorAddress * imageInfo.SectorSize), buffer, 0, + length * imageInfo.SectorSize); + else + { + Stream stream = a2MgImageFilter.GetDataForkStream(); + stream.Seek((long)(imageHeader.DataOffset + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + } + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Structs.cs b/DiscImageChef.DiscImages/Apple2MG/Structs.cs new file mode 100644 index 000000000..db5d87e0e --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Structs.cs @@ -0,0 +1,114 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct A2ImgHeader + { + /// + /// Offset 0x00, magic + /// + public uint Magic; + /// + /// Offset 0x04, disk image creator ID + /// + public uint Creator; + /// + /// Offset 0x08, header size, constant 0x0040 + /// + public ushort HeaderSize; + /// + /// Offset 0x0A, disk image version + /// + public ushort Version; + /// + /// Offset 0x0C, disk image format + /// + public SectorOrder ImageFormat; + /// + /// Offset 0x10, flags and volume number + /// + public uint Flags; + /// + /// Offset 0x14, blocks for ProDOS, 0 otherwise + /// + public uint Blocks; + /// + /// Offset 0x18, offset to data + /// + public uint DataOffset; + /// + /// Offset 0x1C, data size in bytes + /// + public uint DataSize; + /// + /// Offset 0x20, offset to optional comment + /// + public uint CommentOffset; + /// + /// Offset 0x24, length of optional comment + /// + public uint CommentSize; + /// + /// Offset 0x28, offset to creator specific chunk + /// + public uint CreatorSpecificOffset; + /// + /// Offset 0x2C, creator specific chunk size + /// + public uint CreatorSpecificSize; + /// + /// Offset 0x30, reserved, should be zero + /// + public uint Reserved1; + /// + /// Offset 0x34, reserved, should be zero + /// + public uint Reserved2; + /// + /// Offset 0x38, reserved, should be zero + /// + public uint Reserved3; + /// + /// Offset 0x3C, reserved, should be zero + /// + public uint Reserved4; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Unsupported.cs b/DiscImageChef.DiscImages/Apple2MG/Unsupported.cs new file mode 100644 index 000000000..734354e26 --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, + out List unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apple2MG/Write.cs b/DiscImageChef.DiscImages/Apple2MG/Write.cs new file mode 100644 index 000000000..17fde7b28 --- /dev/null +++ b/DiscImageChef.DiscImages/Apple2MG/Write.cs @@ -0,0 +1,257 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes XGS emulator disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Apple2Mg + { + public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + if(sectorSize != 256 || mediaType != MediaType.Apple32SS && mediaType != MediaType.Apple33SS) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + if(sectors > uint.MaxValue) + { + ErrorMessage = "Too many sectors"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(0x40 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(0x40 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + writingStream.Seek(0x40 + 17 * 16 * 256, SeekOrigin.Begin); + byte[] tmp = new byte[256]; + writingStream.Read(tmp, 0, tmp.Length); + bool isDos = tmp[0x01] == 17 && tmp[0x02] < 16 && tmp[0x27] <= 122 && tmp[0x34] == 35 && tmp[0x35] == 16 && + tmp[0x36] == 0 && tmp[0x37] == 1; + + imageHeader = new A2ImgHeader + { + Blocks = (uint)(imageInfo.Sectors * imageInfo.SectorSize) / 512, + Creator = CREATOR_DIC, + DataOffset = 0x40, + DataSize = (uint)(imageInfo.Sectors * imageInfo.SectorSize), + Flags = + (uint)(imageInfo.LastMediaSequence != 0 + ? VALID_VOLUME_NUMBER + (imageInfo.MediaSequence & 0xFF) + : 0), + HeaderSize = 0x40, + ImageFormat = isDos ? SectorOrder.Dos : SectorOrder.ProDos, + Magic = MAGIC, + Version = 1 + }; + + if(!string.IsNullOrEmpty(imageInfo.Comments)) + { + writingStream.Seek(0, SeekOrigin.End); + tmp = Encoding.UTF8.GetBytes(imageInfo.Comments); + imageHeader.CommentOffset = (uint)writingStream.Position; + imageHeader.CommentSize = (uint)(tmp.Length + 1); + writingStream.Write(tmp, 0, tmp.Length); + writingStream.WriteByte(0); + } + + byte[] hdr = new byte[Marshal.SizeOf(typeof(A2ImgHeader))]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(A2ImgHeader))); + Marshal.StructureToPtr(imageHeader, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.Comments = metadata.Comments; + imageInfo.LastMediaSequence = metadata.LastMediaSequence; + imageInfo.MediaSequence = metadata.MediaSequence; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetDumpHardware(List dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS.cs b/DiscImageChef.DiscImages/AppleDOS.cs deleted file mode 100644 index 5b12f4afc..000000000 --- a/DiscImageChef.DiscImages/AppleDOS.cs +++ /dev/null @@ -1,447 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : AppleDOS.cs -// Author(s) : Natalia Portillo -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages interleaved Apple ][ disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see . -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class AppleDos : IWritableImage - { - readonly int[] deinterleave = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - readonly int[] interleave = {0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15}; - - byte[] deinterleaved; - string extension; - ImageInfo imageInfo; - FileStream writingStream; - - public AppleDos() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Apple ][ Interleaved Disk Image"; - public Guid Id => new Guid("A5828AC0-62C9-4304-81D4-EFD4AAE47360"); - - public string Format => - extension == ".po" - ? "Apple ][ Interleaved Disk Image (ProDOS order)" - : "Apple ][ Interleaved Disk Image (DOS order)"; - - public List Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - extension = Path.GetExtension(imageFilter.GetFilename())?.ToLower(); - - return imageFilter.GetDataForkLength() == 143360 && (extension == ".po" || extension == ".do"); - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - byte[] tmp = new byte[imageFilter.GetDataForkLength()]; - stream.Read(tmp, 0, tmp.Length); - - bool isDos = tmp[0x11001] == 17 && tmp[0x11002] < 16 && tmp[0x11027] <= 122 && tmp[0x11034] == 35 && - tmp[0x11035] == 16 && tmp[0x11036] == 0 && tmp[0x11037] == 1; - - deinterleaved = new byte[tmp.Length]; - - extension = Path.GetExtension(imageFilter.GetFilename())?.ToLower(); - - int[] offsets = extension == ".do" - ? (isDos ? deinterleave : interleave) - : (isDos ? interleave : deinterleave); - - for(int t = 0; t < 35; t++) - { - for(int s = 0; s < 16; s++) - Array.Copy(tmp, t * 16 * 256 + s * 256, deinterleaved, t * 16 * 256 + offsets[s] * 256, 256); - } - - imageInfo.SectorSize = 256; - imageInfo.ImageSize = (ulong)imageFilter.GetDataForkLength(); - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = 560; - imageInfo.MediaType = MediaType.Apple33SS; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.Cylinders = 35; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 16; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Array.Copy(deinterleaved, (int)(sectorAddress * imageInfo.SectorSize), buffer, 0, buffer.Length); - - return buffer; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public List GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public IEnumerable SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable SupportedMediaTypes => new[] {MediaType.Apple33SS}; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable KnownExtensions => new[] {".do", ".po"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 256) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(mediaType != MediaType.Apple33SS) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - if(sectors > uint.MaxValue) - { - ErrorMessage = "Too many sectors"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - deinterleaved = new byte[35 * 16 * 256]; - extension = Path.GetExtension(path); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - return WriteSectors(data, sectorAddress, 1); - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - Array.Copy(data, 0, deinterleaved, (int)(sectorAddress * imageInfo.SectorSize), data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - bool isDos = deinterleaved[0x11001] == 17 && deinterleaved[0x11002] < 16 && - deinterleaved[0x11027] <= 122 && - deinterleaved[0x11034] == 35 && deinterleaved[0x11035] == 16 && - deinterleaved[0x11036] == 0 && - deinterleaved[0x11037] == 1; - - byte[] tmp = new byte[deinterleaved.Length]; - - int[] offsets = extension == ".do" - ? (isDos ? deinterleave : interleave) - : (isDos ? interleave : deinterleave); - - for(int t = 0; t < 35; t++) - { - for(int s = 0; s < 16; s++) - Array.Copy(deinterleaved, t * 16 * 256 + offsets[s] * 256, tmp, t * 16 * 256 + s * 256, 256); - } - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(tmp, 0, tmp.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetDumpHardware(List dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS/AppleDOS.cs b/DiscImageChef.DiscImages/AppleDOS/AppleDOS.cs new file mode 100644 index 000000000..af3bac194 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleDOS/AppleDOS.cs @@ -0,0 +1,75 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : AppleDOS.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages interleaved Apple ][ disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleDos : IWritableImage + { + byte[] deinterleaved; + string extension; + ImageInfo imageInfo; + FileStream writingStream; + + public AppleDos() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS/Constants.cs b/DiscImageChef.DiscImages/AppleDOS/Constants.cs new file mode 100644 index 000000000..5157ab8b5 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleDOS/Constants.cs @@ -0,0 +1,40 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for interleaved Apple ][ disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class AppleDos + { + readonly int[] deinterleave = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + readonly int[] interleave = {0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS/Identify.cs b/DiscImageChef.DiscImages/AppleDOS/Identify.cs new file mode 100644 index 000000000..6c564751d --- /dev/null +++ b/DiscImageChef.DiscImages/AppleDOS/Identify.cs @@ -0,0 +1,47 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies interleaved Apple ][ disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleDos + { + public bool Identify(IFilter imageFilter) + { + extension = Path.GetExtension(imageFilter.GetFilename())?.ToLower(); + + return imageFilter.GetDataForkLength() == 143360 && (extension == ".po" || extension == ".do"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS/Properties.cs b/DiscImageChef.DiscImages/AppleDOS/Properties.cs new file mode 100644 index 000000000..52e69837c --- /dev/null +++ b/DiscImageChef.DiscImages/AppleDOS/Properties.cs @@ -0,0 +1,73 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for interleaved Apple ][ disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleDos + { + public ImageInfo Info => imageInfo; + + public string Name => "Apple ][ Interleaved Disk Image"; + public Guid Id => new Guid("A5828AC0-62C9-4304-81D4-EFD4AAE47360"); + + public string Format => + extension == ".po" + ? "Apple ][ Interleaved Disk Image (ProDOS order)" + : "Apple ][ Interleaved Disk Image (DOS order)"; + + public List Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public List Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public IEnumerable SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable SupportedMediaTypes => new[] {MediaType.Apple33SS}; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable KnownExtensions => new[] {".do", ".po"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS/Read.cs b/DiscImageChef.DiscImages/AppleDOS/Read.cs new file mode 100644 index 000000000..665f47a8c --- /dev/null +++ b/DiscImageChef.DiscImages/AppleDOS/Read.cs @@ -0,0 +1,103 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads interleaved Apple ][ disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleDos + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + byte[] tmp = new byte[imageFilter.GetDataForkLength()]; + stream.Read(tmp, 0, tmp.Length); + + bool isDos = tmp[0x11001] == 17 && tmp[0x11002] < 16 && tmp[0x11027] <= 122 && tmp[0x11034] == 35 && + tmp[0x11035] == 16 && tmp[0x11036] == 0 && tmp[0x11037] == 1; + + deinterleaved = new byte[tmp.Length]; + + extension = Path.GetExtension(imageFilter.GetFilename())?.ToLower(); + + int[] offsets = extension == ".do" + ? (isDos ? deinterleave : interleave) + : (isDos ? interleave : deinterleave); + + for(int t = 0; t < 35; t++) + { + for(int s = 0; s < 16; s++) + Array.Copy(tmp, t * 16 * 256 + s * 256, deinterleaved, t * 16 * 256 + offsets[s] * 256, 256); + } + + imageInfo.SectorSize = 256; + imageInfo.ImageSize = (ulong)imageFilter.GetDataForkLength(); + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = 560; + imageInfo.MediaType = MediaType.Apple33SS; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.Cylinders = 35; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 16; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Array.Copy(deinterleaved, (int)(sectorAddress * imageInfo.SectorSize), buffer, 0, buffer.Length); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS/Unsupported.cs b/DiscImageChef.DiscImages/AppleDOS/Unsupported.cs new file mode 100644 index 000000000..18de43bb4 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleDOS/Unsupported.cs @@ -0,0 +1,144 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by interleaved Apple ][ disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleDos + { + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + + public List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleDOS/Write.cs b/DiscImageChef.DiscImages/AppleDOS/Write.cs new file mode 100644 index 000000000..08ec09516 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleDOS/Write.cs @@ -0,0 +1,210 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes interleaved Apple ][ disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleDos + { + public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 256) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(mediaType != MediaType.Apple33SS) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + if(sectors > uint.MaxValue) + { + ErrorMessage = "Too many sectors"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + deinterleaved = new byte[35 * 16 * 256]; + extension = Path.GetExtension(path); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + return WriteSectors(data, sectorAddress, 1); + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + Array.Copy(data, 0, deinterleaved, (int)(sectorAddress * imageInfo.SectorSize), data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + bool isDos = deinterleaved[0x11001] == 17 && deinterleaved[0x11002] < 16 && + deinterleaved[0x11027] <= 122 && + deinterleaved[0x11034] == 35 && deinterleaved[0x11035] == 16 && + deinterleaved[0x11036] == 0 && + deinterleaved[0x11037] == 1; + + byte[] tmp = new byte[deinterleaved.Length]; + + int[] offsets = extension == ".do" + ? (isDos ? deinterleave : interleave) + : (isDos ? interleave : deinterleave); + + for(int t = 0; t < 35; t++) + { + for(int s = 0; s < 16; s++) + Array.Copy(deinterleaved, t * 16 * 256 + offsets[s] * 256, tmp, t * 16 * 256 + s * 256, 256); + } + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(tmp, 0, tmp.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetDumpHardware(List dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleNIB/AppleNIB.cs b/DiscImageChef.DiscImages/AppleNIB/AppleNIB.cs new file mode 100644 index 000000000..4fd5d9253 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleNIB/AppleNIB.cs @@ -0,0 +1,75 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : AppleNIB.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Apple nibbelized disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Checksum sectors + public partial class AppleNib : IMediaImage + { + Dictionary addressFields; + Dictionary cookedSectors; + ImageInfo imageInfo; + Dictionary longSectors; + + public AppleNib() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleNIB/Constants.cs b/DiscImageChef.DiscImages/AppleNIB/Constants.cs new file mode 100644 index 000000000..89819ce68 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleNIB/Constants.cs @@ -0,0 +1,53 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Apple nibbelized disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class AppleNib + { + readonly byte[] apple3_sign = {0x8D, 0xD0, 0x03, 0x4C, 0xC7, 0xA4}; + readonly byte[] cpm_sign = {0xA2, 0x55, 0xA9, 0x00, 0x9D, 0x00, 0x0D, 0xCA}; + readonly byte[] dos_sign = {0xA2, 0x02, 0x8E, 0x52}; + readonly ulong[] dosSkewing = {0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15}; + readonly byte[] dri_string = + { + 0x43, 0x4F, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x28, 0x43, 0x29, 0x20, 0x31, 0x39, 0x37, 0x39, + 0x2C, 0x20, 0x44, 0x49, 0x47, 0x49, 0x54, 0x41, 0x4C, 0x20, 0x52, 0x45, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48 + }; + readonly byte[] pascal_sign = {0x08, 0xA5, 0x0F, 0x29}; + readonly byte[] pascal_string = {0x53, 0x59, 0x53, 0x54, 0x45, 0x2E, 0x41, 0x50, 0x50, 0x4C, 0x45}; + readonly byte[] pascal2_sign = {0xFF, 0xA2, 0x00, 0x8E}; + readonly byte[] prodos_string = {0x50, 0x52, 0x4F, 0x44, 0x4F, 0x53}; + readonly ulong[] proDosSkewing = {0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15}; + readonly byte[] sos_sign = {0xC9, 0x20, 0xF0, 0x3E}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleNIB/Helpers.cs b/DiscImageChef.DiscImages/AppleNIB/Helpers.cs new file mode 100644 index 000000000..822184a8d --- /dev/null +++ b/DiscImageChef.DiscImages/AppleNIB/Helpers.cs @@ -0,0 +1,49 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Apple nibbelized disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleNib + { + MediaType GetMediaType() + { + switch(imageInfo.Sectors) + { + case 455: return MediaType.Apple32SS; + case 560: return MediaType.Apple33SS; + default: return MediaType.Unknown; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleNIB/Identify.cs b/DiscImageChef.DiscImages/AppleNIB/Identify.cs new file mode 100644 index 000000000..569b060ab --- /dev/null +++ b/DiscImageChef.DiscImages/AppleNIB/Identify.cs @@ -0,0 +1,54 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Apple nibbelized disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Decoders.Floppy; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleNib + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] test = new byte[512]; + stream.Read(test, 0, 512); + + return Apple2.IsApple2GCR(test); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleNIB/Properties.cs b/DiscImageChef.DiscImages/AppleNIB/Properties.cs new file mode 100644 index 000000000..edfc8dc82 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleNIB/Properties.cs @@ -0,0 +1,62 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Apple nibbelized disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleNib + { + public ImageInfo Info => imageInfo; + + public string Name => "Apple NIB"; + public Guid Id => new Guid("AE171AE8-6747-49CC-B861-9D450B7CD42E"); + + public string Format => "Apple nibbles"; + + public List Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleNIB.cs b/DiscImageChef.DiscImages/AppleNIB/Read.cs similarity index 60% rename from DiscImageChef.DiscImages/AppleNIB.cs rename to DiscImageChef.DiscImages/AppleNIB/Read.cs index 2380a0b07..25c56cb0a 100644 --- a/DiscImageChef.DiscImages/AppleNIB.cs +++ b/DiscImageChef.DiscImages/AppleNIB/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : AppleNIB.cs +// Filename : Read.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Apple nibbelized disc images. +// Reads Apple nibbelized disk images. // // --[ License ] -------------------------------------------------------------- // @@ -38,92 +38,13 @@ using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Decoders.Floppy; -using Schemas; namespace DiscImageChef.DiscImages { - // TODO: Checksum sectors - public class AppleNib : IMediaImage + public partial class AppleNib { - readonly byte[] apple3_sign = {0x8D, 0xD0, 0x03, 0x4C, 0xC7, 0xA4}; - readonly byte[] cpm_sign = {0xA2, 0x55, 0xA9, 0x00, 0x9D, 0x00, 0x0D, 0xCA}; - readonly byte[] dos_sign = {0xA2, 0x02, 0x8E, 0x52}; - readonly ulong[] dosSkewing = {0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15}; - readonly byte[] dri_string = - { - 0x43, 0x4F, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x28, 0x43, 0x29, 0x20, 0x31, 0x39, 0x37, 0x39, - 0x2C, 0x20, 0x44, 0x49, 0x47, 0x49, 0x54, 0x41, 0x4C, 0x20, 0x52, 0x45, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48 - }; - readonly byte[] pascal_sign = {0x08, 0xA5, 0x0F, 0x29}; - readonly byte[] pascal_string = {0x53, 0x59, 0x53, 0x54, 0x45, 0x2E, 0x41, 0x50, 0x50, 0x4C, 0x45}; - readonly byte[] pascal2_sign = {0xFF, 0xA2, 0x00, 0x8E}; - readonly byte[] prodos_string = {0x50, 0x52, 0x4F, 0x44, 0x4F, 0x53}; - readonly ulong[] proDosSkewing = {0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15}; - readonly byte[] sos_sign = {0xC9, 0x20, 0xF0, 0x3E}; - Dictionary addressFields; - Dictionary cookedSectors; - ImageInfo imageInfo; - Dictionary longSectors; - - public AppleNib() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Apple NIB"; - public Guid Id => new Guid("AE171AE8-6747-49CC-B861-9D450B7CD42E"); - - public string Format => "Apple nibbles"; - - public List Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] test = new byte[512]; - stream.Read(test, 0, 512); - - return Apple2.IsApple2GCR(test); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -328,94 +249,5 @@ namespace DiscImageChef.DiscImages return ms.ToArray(); } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, - out List unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - MediaType GetMediaType() - { - switch(imageInfo.Sectors) - { - case 455: return MediaType.Apple32SS; - case 560: return MediaType.Apple33SS; - default: return MediaType.Unknown; - } - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/AppleNIB/Unsupported.cs b/DiscImageChef.DiscImages/AppleNIB/Unsupported.cs new file mode 100644 index 000000000..41b33ed37 --- /dev/null +++ b/DiscImageChef.DiscImages/AppleNIB/Unsupported.cs @@ -0,0 +1,118 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Apple nibbelized disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class AppleNib + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, + out List unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk.cs b/DiscImageChef.DiscImages/Apridisk.cs deleted file mode 100644 index bebf6c2b4..000000000 --- a/DiscImageChef.DiscImages/Apridisk.cs +++ /dev/null @@ -1,772 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : Apridisk.cs -// Author(s) : Natalia Portillo -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Apridisk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see . -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - // TODO: Check writing - public class Apridisk : IWritableImage - { - readonly byte[] signature = - { - 0x41, 0x43, 0x54, 0x20, 0x41, 0x70, 0x72, 0x69, 0x63, 0x6F, 0x74, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x20, 0x69, - 0x6D, 0x61, 0x67, 0x65, 0x1A, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00 - }; - ImageInfo imageInfo; - - // Cylinder by head, sector data matrix - byte[][][][] sectorsData; - FileStream writingStream; - - public Apridisk() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "ACT Apricot Disk Image"; - public Guid Id => new Guid("43408CF3-6DB3-449F-A779-2B0E497C5B14"); - - public string Format => "ACT Apricot disk image"; - - public List Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < signature.Length) return false; - - byte[] sigB = new byte[signature.Length]; - stream.Read(sigB, 0, signature.Length); - - return sigB.SequenceEqual(signature); - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - // Skip signature - stream.Seek(signature.Length, SeekOrigin.Begin); - - int totalCylinders = -1; - int totalHeads = -1; - int maxSector = -1; - int recordSize = Marshal.SizeOf(typeof(ApridiskRecord)); - - // Count cylinders - while(stream.Position < stream.Length) - { - byte[] recB = new byte[recordSize]; - stream.Read(recB, 0, recordSize); - - GCHandle handle = GCHandle.Alloc(recB, GCHandleType.Pinned); - ApridiskRecord record = - (ApridiskRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ApridiskRecord)); - handle.Free(); - - switch(record.type) - { - // Deleted record, just skip it - case RecordType.Deleted: - DicConsole.DebugWriteLine("Apridisk plugin", "Found deleted record at {0}", stream.Position); - stream.Seek(record.headerSize - recordSize + record.dataSize, SeekOrigin.Current); - break; - case RecordType.Comment: - DicConsole.DebugWriteLine("Apridisk plugin", "Found comment record at {0}", stream.Position); - stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); - byte[] commentB = new byte[record.dataSize]; - stream.Read(commentB, 0, commentB.Length); - imageInfo.Comments = StringHandlers.CToString(commentB); - DicConsole.DebugWriteLine("Apridisk plugin", "Comment: \"{0}\"", imageInfo.Comments); - break; - case RecordType.Creator: - DicConsole.DebugWriteLine("Apridisk plugin", "Found creator record at {0}", stream.Position); - stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); - byte[] creatorB = new byte[record.dataSize]; - stream.Read(creatorB, 0, creatorB.Length); - imageInfo.Creator = StringHandlers.CToString(creatorB); - DicConsole.DebugWriteLine("Apridisk plugin", "Creator: \"{0}\"", imageInfo.Creator); - break; - case RecordType.Sector: - if(record.compression != CompressType.Compressed && - record.compression != CompressType.Uncompresed) - throw new - ImageNotSupportedException($"Found record with unknown compression type 0x{(ushort)record.compression:X4} at {stream.Position}"); - - DicConsole.DebugWriteLine("Apridisk plugin", - "Found {4} sector record at {0} for cylinder {1} head {2} sector {3}", - stream.Position, record.cylinder, record.head, record.sector, - record.compression == CompressType.Compressed - ? "compressed" - : "uncompressed"); - - if(record.cylinder > totalCylinders) totalCylinders = record.cylinder; - if(record.head > totalHeads) totalHeads = record.head; - if(record.sector > maxSector) maxSector = record.sector; - - stream.Seek(record.headerSize - recordSize + record.dataSize, SeekOrigin.Current); - break; - default: - throw new - ImageNotSupportedException($"Found record with unknown type 0x{(uint)record.type:X8} at {stream.Position}"); - } - } - - totalCylinders++; - totalHeads++; - - if(totalCylinders <= 0 || totalHeads <= 0) - throw new ImageNotSupportedException("No cylinders or heads found"); - - sectorsData = new byte[totalCylinders][][][]; - // Total sectors per track - uint[][] spts = new uint[totalCylinders][]; - - imageInfo.Cylinders = (ushort)totalCylinders; - imageInfo.Heads = (byte)totalHeads; - - DicConsole.DebugWriteLine("Apridisk plugin", - "Found {0} cylinders and {1} heads with a maximum sector number of {2}", - totalCylinders, totalHeads, maxSector); - - // Create heads - for(int i = 0; i < totalCylinders; i++) - { - sectorsData[i] = new byte[totalHeads][][]; - spts[i] = new uint[totalHeads]; - - for(int j = 0; j < totalHeads; j++) sectorsData[i][j] = new byte[maxSector + 1][]; - } - - imageInfo.SectorSize = uint.MaxValue; - - ulong headersizes = 0; - - // Read sectors - stream.Seek(signature.Length, SeekOrigin.Begin); - while(stream.Position < stream.Length) - { - byte[] recB = new byte[recordSize]; - stream.Read(recB, 0, recordSize); - - GCHandle handle = GCHandle.Alloc(recB, GCHandleType.Pinned); - ApridiskRecord record = - (ApridiskRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ApridiskRecord)); - handle.Free(); - - switch(record.type) - { - // Not sector record, just skip it - case RecordType.Deleted: - case RecordType.Comment: - case RecordType.Creator: - stream.Seek(record.headerSize - recordSize + record.dataSize, SeekOrigin.Current); - headersizes += record.headerSize + record.dataSize; - break; - case RecordType.Sector: - stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); - - byte[] data = new byte[record.dataSize]; - stream.Read(data, 0, data.Length); - - spts[record.cylinder][record.head]++; - uint realLength = record.dataSize; - - if(record.compression == CompressType.Compressed) - realLength = - Decompress(data, out sectorsData[record.cylinder][record.head][record.sector]); - else sectorsData[record.cylinder][record.head][record.sector] = data; - - if(realLength < imageInfo.SectorSize) imageInfo.SectorSize = realLength; - - headersizes += record.headerSize + record.dataSize; - - break; - } - } - - DicConsole.DebugWriteLine("Apridisk plugin", "Found a minimum of {0} bytes per sector", - imageInfo.SectorSize); - - // Count sectors per track - uint spt = uint.MaxValue; - for(ushort cyl = 0; cyl < imageInfo.Cylinders; cyl++) - { - for(ushort head = 0; head < imageInfo.Heads; head++) - if(spts[cyl][head] < spt) - spt = spts[cyl][head]; - } - - imageInfo.SectorsPerTrack = spt; - - DicConsole.DebugWriteLine("Apridisk plugin", "Found a minimum of {0} sectors per track", - imageInfo.SectorsPerTrack); - - imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, - (ushort)imageInfo.SectorsPerTrack, 512, MediaEncoding.MFM, - false)); - - imageInfo.ImageSize = (ulong)stream.Length - headersizes; - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = imageInfo.Cylinders * imageInfo.Heads * imageInfo.SectorsPerTrack; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - (ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress); - - if(cylinder >= sectorsData.Length) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(head >= sectorsData[cylinder].Length) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sector > sectorsData[cylinder][head].Length) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - return sectorsData[cylinder][head][sector]; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream buffer = new MemoryStream(); - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - buffer.Write(sector, 0, sector.Length); - } - - return buffer.ToArray(); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, - out List unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable SupportedSectorTags => new SectorTagType[] { }; - // TODO: Test with real hardware to see real supported media - public IEnumerable SupportedMediaTypes => - new[] - { - MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, - MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_SS_DD_11, MediaType.DMF, - MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, - MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, - MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, - MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, - MediaType.FDFORMAT_525_HD, MediaType.RX50, MediaType.XDF_35, MediaType.XDF_525 - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new[] {("compress", typeof(bool), "Enable Apridisk compression.")}; - public IEnumerable KnownExtensions => new[] {".dsk"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, - uint sectorSize) - { - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - (ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress); - - if(cylinder >= sectorsData.Length) - { - ErrorMessage = "Sector address not found"; - return false; - } - - if(head >= sectorsData[cylinder].Length) - { - ErrorMessage = "Sector address not found"; - return false; - } - - if(sector > sectorsData[cylinder][head].Length) - { - ErrorMessage = "Sector address not found"; - return false; - } - - sectorsData[cylinder][head][sector] = data; - - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - for(uint i = 0; i < length; i++) - { - (ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress); - - if(cylinder >= sectorsData.Length) - { - ErrorMessage = "Sector address not found"; - return false; - } - - if(head >= sectorsData[cylinder].Length) - { - ErrorMessage = "Sector address not found"; - return false; - } - - if(sector > sectorsData[cylinder][head].Length) - { - ErrorMessage = "Sector address not found"; - return false; - } - - sectorsData[cylinder][head][sector] = data; - } - - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - // TODO: Try if apridisk software supports finding other chunks, to extend metadata support - public bool Close() - { - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(signature, 0, signature.Length); - - byte[] hdr = new byte[Marshal.SizeOf(typeof(ApridiskRecord))]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ApridiskRecord))); - - for(ushort c = 0; c < imageInfo.Cylinders; c++) - { - for(byte h = 0; h < imageInfo.Heads; h++) - { - for(byte s = 0; s < imageInfo.SectorsPerTrack; s++) - { - if(sectorsData[c][h][s] == null || sectorsData[c][h][s].Length == 0) continue; - - ApridiskRecord record = new ApridiskRecord - { - type = RecordType.Sector, - compression = CompressType.Uncompresed, - headerSize = (ushort)Marshal.SizeOf(typeof(ApridiskRecord)), - dataSize = (uint)sectorsData[c][h][s].Length, - head = h, - sector = s, - cylinder = c - }; - - Marshal.StructureToPtr(record, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - - writingStream.Write(hdr, 0, hdr.Length); - writingStream.Write(sectorsData[c][h][s], 0, sectorsData[c][h][s].Length); - } - } - } - - if(!string.IsNullOrEmpty(imageInfo.Creator)) - { - byte[] creatorBytes = Encoding.UTF8.GetBytes(imageInfo.Creator); - ApridiskRecord creatorRecord = new ApridiskRecord - { - type = RecordType.Creator, - compression = CompressType.Uncompresed, - headerSize = (ushort)Marshal.SizeOf(typeof(ApridiskRecord)), - dataSize = (uint)creatorBytes.Length + 1, - head = 0, - sector = 0, - cylinder = 0 - }; - - Marshal.StructureToPtr(creatorRecord, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - - writingStream.Write(hdr, 0, hdr.Length); - writingStream.Write(creatorBytes, 0, creatorBytes.Length); - writingStream.WriteByte(0); // Termination - } - - if(!string.IsNullOrEmpty(imageInfo.Comments)) - { - byte[] commentBytes = Encoding.UTF8.GetBytes(imageInfo.Comments); - ApridiskRecord commentRecord = new ApridiskRecord - { - type = RecordType.Comment, - compression = CompressType.Uncompresed, - headerSize = (ushort)Marshal.SizeOf(typeof(ApridiskRecord)), - dataSize = (uint)commentBytes.Length + 1, - head = 0, - sector = 0, - cylinder = 0 - }; - - Marshal.StructureToPtr(commentRecord, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - - writingStream.Write(hdr, 0, hdr.Length); - writingStream.Write(commentBytes, 0, commentBytes.Length); - writingStream.WriteByte(0); // Termination - } - - Marshal.FreeHGlobal(hdrPtr); - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.Comments = metadata.Comments; - imageInfo.Creator = metadata.Creator; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > ushort.MaxValue) - { - ErrorMessage = "Too many cylinders"; - return false; - } - - if(heads > byte.MaxValue) - { - ErrorMessage = "Too many heads"; - return false; - } - - if(sectorsPerTrack > byte.MaxValue) - { - ErrorMessage = "Too many sectors per track"; - return false; - } - - sectorsData = new byte[cylinders][][][]; - for(ushort c = 0; c < cylinders; c++) - { - sectorsData[c] = new byte[heads][][]; - for(byte h = 0; h < heads; h++) sectorsData[c][h] = new byte[sectorsPerTrack][]; - } - - imageInfo.Cylinders = cylinders; - imageInfo.Heads = heads; - imageInfo.SectorsPerTrack = sectorsPerTrack; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - static uint Decompress(byte[] compressed, out byte[] decompressed) - { - int readp = 0; - int cLen = compressed.Length; - MemoryStream buffer = new MemoryStream(); - - uint uLen = 0; - - while(cLen >= 3) - { - ushort blklen = BitConverter.ToUInt16(compressed, readp); - readp += 2; - - for(int i = 0; i < blklen; i++) buffer.WriteByte(compressed[readp]); - - uLen += blklen; - readp++; - cLen -= 3; - } - - decompressed = buffer.ToArray(); - return uLen; - } - - (ushort cylinder, byte head, byte sector) LbaToChs(ulong lba) - { - ushort cylinder = (ushort)(lba / (imageInfo.Heads * imageInfo.SectorsPerTrack)); - byte head = (byte)(lba / imageInfo.SectorsPerTrack % imageInfo.Heads); - byte sector = (byte)(lba % imageInfo.SectorsPerTrack + 1); - - return (cylinder, head, sector); - } - - enum RecordType : uint - { - Deleted = 0xE31D0000, - Sector = 0xE31D0001, - Comment = 0xE31D0002, - Creator = 0xE31D0003 - } - - enum CompressType : ushort - { - Uncompresed = 0x9E90, - Compressed = 0x3E5A - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ApridiskRecord - { - public RecordType type; - public CompressType compression; - public ushort headerSize; - public uint dataSize; - public byte head; - public byte sector; - public ushort cylinder; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Apridisk.cs b/DiscImageChef.DiscImages/Apridisk/Apridisk.cs new file mode 100644 index 000000000..f7c61d183 --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Apridisk.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Apridisk.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Apridisk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Check writing + public partial class Apridisk : IWritableImage + { + ImageInfo imageInfo; + + // Cylinder by head, sector data matrix + byte[][][][] sectorsData; + FileStream writingStream; + + public Apridisk() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Compression.cs b/DiscImageChef.DiscImages/Apridisk/Compression.cs new file mode 100644 index 000000000..edf7d80c5 --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Compression.cs @@ -0,0 +1,64 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Compression.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains compression algorithm for Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + static uint Decompress(byte[] compressed, out byte[] decompressed) + { + int readp = 0; + int cLen = compressed.Length; + MemoryStream buffer = new MemoryStream(); + + uint uLen = 0; + + while(cLen >= 3) + { + ushort blklen = BitConverter.ToUInt16(compressed, readp); + readp += 2; + + for(int i = 0; i < blklen; i++) buffer.WriteByte(compressed[readp]); + + uLen += blklen; + readp++; + cLen -= 3; + } + + decompressed = buffer.ToArray(); + return uLen; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Constants.cs b/DiscImageChef.DiscImages/Apridisk/Constants.cs new file mode 100644 index 000000000..de9cce4ea --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Constants.cs @@ -0,0 +1,49 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + readonly byte[] signature = + { + 0x41, 0x43, 0x54, 0x20, 0x41, 0x70, 0x72, 0x69, 0x63, 0x6F, 0x74, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x20, 0x69, + 0x6D, 0x61, 0x67, 0x65, 0x1A, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Enums.cs b/DiscImageChef.DiscImages/Apridisk/Enums.cs new file mode 100644 index 000000000..811b2ae25 --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Enums.cs @@ -0,0 +1,51 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + enum RecordType : uint + { + Deleted = 0xE31D0000, + Sector = 0xE31D0001, + Comment = 0xE31D0002, + Creator = 0xE31D0003 + } + + enum CompressType : ushort + { + Uncompresed = 0x9E90, + Compressed = 0x3E5A + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Helpers.cs b/DiscImageChef.DiscImages/Apridisk/Helpers.cs new file mode 100644 index 000000000..4c1e291ff --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Helpers.cs @@ -0,0 +1,46 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + (ushort cylinder, byte head, byte sector) LbaToChs(ulong lba) + { + ushort cylinder = (ushort)(lba / (imageInfo.Heads * imageInfo.SectorsPerTrack)); + byte head = (byte)(lba / imageInfo.SectorsPerTrack % imageInfo.Heads); + byte sector = (byte)(lba % imageInfo.SectorsPerTrack + 1); + + return (cylinder, head, sector); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Identify.cs b/DiscImageChef.DiscImages/Apridisk/Identify.cs new file mode 100644 index 000000000..81cef2bfb --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Identify.cs @@ -0,0 +1,54 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < signature.Length) return false; + + byte[] sigB = new byte[signature.Length]; + stream.Read(sigB, 0, signature.Length); + + return sigB.SequenceEqual(signature); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Properties.cs b/DiscImageChef.DiscImages/Apridisk/Properties.cs new file mode 100644 index 000000000..8eccd0448 --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Properties.cs @@ -0,0 +1,83 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + public ImageInfo Info => imageInfo; + + public string Name => "ACT Apricot Disk Image"; + public Guid Id => new Guid("43408CF3-6DB3-449F-A779-2B0E497C5B14"); + + public string Format => "ACT Apricot disk image"; + + public List Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + + public IEnumerable SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable SupportedSectorTags => new SectorTagType[] { }; + // TODO: Test with real hardware to see real supported media + public IEnumerable SupportedMediaTypes => + new[] + { + MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, + MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_SS_DD_11, MediaType.DMF, + MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, + MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, + MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, + MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, + MediaType.FDFORMAT_525_HD, MediaType.RX50, MediaType.XDF_35, MediaType.XDF_525 + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new[] {("compress", typeof(bool), "Enable Apridisk compression.")}; + public IEnumerable KnownExtensions => new[] {".dsk"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Read.cs b/DiscImageChef.DiscImages/Apridisk/Read.cs new file mode 100644 index 000000000..ccbcf41a2 --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Read.cs @@ -0,0 +1,256 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + // Skip signature + stream.Seek(signature.Length, SeekOrigin.Begin); + + int totalCylinders = -1; + int totalHeads = -1; + int maxSector = -1; + int recordSize = Marshal.SizeOf(typeof(ApridiskRecord)); + + // Count cylinders + while(stream.Position < stream.Length) + { + byte[] recB = new byte[recordSize]; + stream.Read(recB, 0, recordSize); + + GCHandle handle = GCHandle.Alloc(recB, GCHandleType.Pinned); + ApridiskRecord record = + (ApridiskRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ApridiskRecord)); + handle.Free(); + + switch(record.type) + { + // Deleted record, just skip it + case RecordType.Deleted: + DicConsole.DebugWriteLine("Apridisk plugin", "Found deleted record at {0}", stream.Position); + stream.Seek(record.headerSize - recordSize + record.dataSize, SeekOrigin.Current); + break; + case RecordType.Comment: + DicConsole.DebugWriteLine("Apridisk plugin", "Found comment record at {0}", stream.Position); + stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); + byte[] commentB = new byte[record.dataSize]; + stream.Read(commentB, 0, commentB.Length); + imageInfo.Comments = StringHandlers.CToString(commentB); + DicConsole.DebugWriteLine("Apridisk plugin", "Comment: \"{0}\"", imageInfo.Comments); + break; + case RecordType.Creator: + DicConsole.DebugWriteLine("Apridisk plugin", "Found creator record at {0}", stream.Position); + stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); + byte[] creatorB = new byte[record.dataSize]; + stream.Read(creatorB, 0, creatorB.Length); + imageInfo.Creator = StringHandlers.CToString(creatorB); + DicConsole.DebugWriteLine("Apridisk plugin", "Creator: \"{0}\"", imageInfo.Creator); + break; + case RecordType.Sector: + if(record.compression != CompressType.Compressed && + record.compression != CompressType.Uncompresed) + throw new + ImageNotSupportedException($"Found record with unknown compression type 0x{(ushort)record.compression:X4} at {stream.Position}"); + + DicConsole.DebugWriteLine("Apridisk plugin", + "Found {4} sector record at {0} for cylinder {1} head {2} sector {3}", + stream.Position, record.cylinder, record.head, record.sector, + record.compression == CompressType.Compressed + ? "compressed" + : "uncompressed"); + + if(record.cylinder > totalCylinders) totalCylinders = record.cylinder; + if(record.head > totalHeads) totalHeads = record.head; + if(record.sector > maxSector) maxSector = record.sector; + + stream.Seek(record.headerSize - recordSize + record.dataSize, SeekOrigin.Current); + break; + default: + throw new + ImageNotSupportedException($"Found record with unknown type 0x{(uint)record.type:X8} at {stream.Position}"); + } + } + + totalCylinders++; + totalHeads++; + + if(totalCylinders <= 0 || totalHeads <= 0) + throw new ImageNotSupportedException("No cylinders or heads found"); + + sectorsData = new byte[totalCylinders][][][]; + // Total sectors per track + uint[][] spts = new uint[totalCylinders][]; + + imageInfo.Cylinders = (ushort)totalCylinders; + imageInfo.Heads = (byte)totalHeads; + + DicConsole.DebugWriteLine("Apridisk plugin", + "Found {0} cylinders and {1} heads with a maximum sector number of {2}", + totalCylinders, totalHeads, maxSector); + + // Create heads + for(int i = 0; i < totalCylinders; i++) + { + sectorsData[i] = new byte[totalHeads][][]; + spts[i] = new uint[totalHeads]; + + for(int j = 0; j < totalHeads; j++) sectorsData[i][j] = new byte[maxSector + 1][]; + } + + imageInfo.SectorSize = uint.MaxValue; + + ulong headersizes = 0; + + // Read sectors + stream.Seek(signature.Length, SeekOrigin.Begin); + while(stream.Position < stream.Length) + { + byte[] recB = new byte[recordSize]; + stream.Read(recB, 0, recordSize); + + GCHandle handle = GCHandle.Alloc(recB, GCHandleType.Pinned); + ApridiskRecord record = + (ApridiskRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ApridiskRecord)); + handle.Free(); + + switch(record.type) + { + // Not sector record, just skip it + case RecordType.Deleted: + case RecordType.Comment: + case RecordType.Creator: + stream.Seek(record.headerSize - recordSize + record.dataSize, SeekOrigin.Current); + headersizes += record.headerSize + record.dataSize; + break; + case RecordType.Sector: + stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); + + byte[] data = new byte[record.dataSize]; + stream.Read(data, 0, data.Length); + + spts[record.cylinder][record.head]++; + uint realLength = record.dataSize; + + if(record.compression == CompressType.Compressed) + realLength = + Decompress(data, out sectorsData[record.cylinder][record.head][record.sector]); + else sectorsData[record.cylinder][record.head][record.sector] = data; + + if(realLength < imageInfo.SectorSize) imageInfo.SectorSize = realLength; + + headersizes += record.headerSize + record.dataSize; + + break; + } + } + + DicConsole.DebugWriteLine("Apridisk plugin", "Found a minimum of {0} bytes per sector", + imageInfo.SectorSize); + + // Count sectors per track + uint spt = uint.MaxValue; + for(ushort cyl = 0; cyl < imageInfo.Cylinders; cyl++) + { + for(ushort head = 0; head < imageInfo.Heads; head++) + if(spts[cyl][head] < spt) + spt = spts[cyl][head]; + } + + imageInfo.SectorsPerTrack = spt; + + DicConsole.DebugWriteLine("Apridisk plugin", "Found a minimum of {0} sectors per track", + imageInfo.SectorsPerTrack); + + imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, + (ushort)imageInfo.SectorsPerTrack, 512, MediaEncoding.MFM, + false)); + + imageInfo.ImageSize = (ulong)stream.Length - headersizes; + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = imageInfo.Cylinders * imageInfo.Heads * imageInfo.SectorsPerTrack; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + (ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress); + + if(cylinder >= sectorsData.Length) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(head >= sectorsData[cylinder].Length) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sector > sectorsData[cylinder][head].Length) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + return sectorsData[cylinder][head][sector]; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream buffer = new MemoryStream(); + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + buffer.Write(sector, 0, sector.Length); + } + + return buffer.ToArray(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Structs.cs b/DiscImageChef.DiscImages/Apridisk/Structs.cs new file mode 100644 index 000000000..55baa9b88 --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Structs.cs @@ -0,0 +1,51 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ApridiskRecord + { + public RecordType type; + public CompressType compression; + public ushort headerSize; + public uint dataSize; + public byte head; + public byte sector; + public ushort cylinder; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Unsupported.cs b/DiscImageChef.DiscImages/Apridisk/Unsupported.cs new file mode 100644 index 000000000..4a91c83bc --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, + out List unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Apridisk/Write.cs b/DiscImageChef.DiscImages/Apridisk/Write.cs new file mode 100644 index 000000000..99d684678 --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk/Write.cs @@ -0,0 +1,307 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Apridisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Apridisk + { + public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, + uint sectorSize) + { + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + (ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress); + + if(cylinder >= sectorsData.Length) + { + ErrorMessage = "Sector address not found"; + return false; + } + + if(head >= sectorsData[cylinder].Length) + { + ErrorMessage = "Sector address not found"; + return false; + } + + if(sector > sectorsData[cylinder][head].Length) + { + ErrorMessage = "Sector address not found"; + return false; + } + + sectorsData[cylinder][head][sector] = data; + + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + for(uint i = 0; i < length; i++) + { + (ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress); + + if(cylinder >= sectorsData.Length) + { + ErrorMessage = "Sector address not found"; + return false; + } + + if(head >= sectorsData[cylinder].Length) + { + ErrorMessage = "Sector address not found"; + return false; + } + + if(sector > sectorsData[cylinder][head].Length) + { + ErrorMessage = "Sector address not found"; + return false; + } + + sectorsData[cylinder][head][sector] = data; + } + + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + // TODO: Try if apridisk software supports finding other chunks, to extend metadata support + public bool Close() + { + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(signature, 0, signature.Length); + + byte[] hdr = new byte[Marshal.SizeOf(typeof(ApridiskRecord))]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ApridiskRecord))); + + for(ushort c = 0; c < imageInfo.Cylinders; c++) + { + for(byte h = 0; h < imageInfo.Heads; h++) + { + for(byte s = 0; s < imageInfo.SectorsPerTrack; s++) + { + if(sectorsData[c][h][s] == null || sectorsData[c][h][s].Length == 0) continue; + + ApridiskRecord record = new ApridiskRecord + { + type = RecordType.Sector, + compression = CompressType.Uncompresed, + headerSize = (ushort)Marshal.SizeOf(typeof(ApridiskRecord)), + dataSize = (uint)sectorsData[c][h][s].Length, + head = h, + sector = s, + cylinder = c + }; + + Marshal.StructureToPtr(record, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + + writingStream.Write(hdr, 0, hdr.Length); + writingStream.Write(sectorsData[c][h][s], 0, sectorsData[c][h][s].Length); + } + } + } + + if(!string.IsNullOrEmpty(imageInfo.Creator)) + { + byte[] creatorBytes = Encoding.UTF8.GetBytes(imageInfo.Creator); + ApridiskRecord creatorRecord = new ApridiskRecord + { + type = RecordType.Creator, + compression = CompressType.Uncompresed, + headerSize = (ushort)Marshal.SizeOf(typeof(ApridiskRecord)), + dataSize = (uint)creatorBytes.Length + 1, + head = 0, + sector = 0, + cylinder = 0 + }; + + Marshal.StructureToPtr(creatorRecord, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + + writingStream.Write(hdr, 0, hdr.Length); + writingStream.Write(creatorBytes, 0, creatorBytes.Length); + writingStream.WriteByte(0); // Termination + } + + if(!string.IsNullOrEmpty(imageInfo.Comments)) + { + byte[] commentBytes = Encoding.UTF8.GetBytes(imageInfo.Comments); + ApridiskRecord commentRecord = new ApridiskRecord + { + type = RecordType.Comment, + compression = CompressType.Uncompresed, + headerSize = (ushort)Marshal.SizeOf(typeof(ApridiskRecord)), + dataSize = (uint)commentBytes.Length + 1, + head = 0, + sector = 0, + cylinder = 0 + }; + + Marshal.StructureToPtr(commentRecord, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + + writingStream.Write(hdr, 0, hdr.Length); + writingStream.Write(commentBytes, 0, commentBytes.Length); + writingStream.WriteByte(0); // Termination + } + + Marshal.FreeHGlobal(hdrPtr); + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.Comments = metadata.Comments; + imageInfo.Creator = metadata.Creator; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > ushort.MaxValue) + { + ErrorMessage = "Too many cylinders"; + return false; + } + + if(heads > byte.MaxValue) + { + ErrorMessage = "Too many heads"; + return false; + } + + if(sectorsPerTrack > byte.MaxValue) + { + ErrorMessage = "Too many sectors per track"; + return false; + } + + sectorsData = new byte[cylinders][][][]; + for(ushort c = 0; c < cylinders; c++) + { + sectorsData[c] = new byte[heads][][]; + for(byte h = 0; h < heads; h++) sectorsData[c][h] = new byte[sectorsPerTrack][]; + } + + imageInfo.Cylinders = cylinders; + imageInfo.Heads = heads; + imageInfo.SectorsPerTrack = sectorsPerTrack; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU.cs b/DiscImageChef.DiscImages/BLU.cs deleted file mode 100644 index 02a7b9714..000000000 --- a/DiscImageChef.DiscImages/BLU.cs +++ /dev/null @@ -1,782 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : BLU.cs -// Author(s) : Natalia Portillo -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Basic Lisa Utility disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see . -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using DiscImageChef.Decoders; -using Schemas; -using Version = DiscImageChef.CommonTypes.Interop.Version; - -namespace DiscImageChef.DiscImages -{ - public class Blu : IWritableImage - { - const string PROFILE_NAME = "PROFILE "; - const string PROFILE10_NAME = "PROFILE 10 "; - const string WIDGET_NAME = "WIDGET-10 "; - const string PRIAM_NAME = "PRIAMDTATOWER"; - IFilter bluImageFilter; - int bptag; - - BluHeader imageHeader; - ImageInfo imageInfo; - FileStream writingStream; - - public Blu() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Basic Lisa Utility"; - public Guid Id => new Guid("A153E2F8-4235-432D-9A7F-20807B0BCD74"); - - public string Format => "Basic Lisa Utility"; - - public List Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 0x200) return false; - - byte[] header = new byte[0x17]; - stream.Read(header, 0, 0x17); - - BluHeader tmpHdr = new BluHeader {DeviceName = new byte[0x0D]}; - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - Array.Copy(header, 0, tmpHdr.DeviceName, 0, 0x0D); - tmpHdr.DeviceType = BigEndianBitConverter.ToUInt32(header, 0x0C) & 0x00FFFFFF; - tmpHdr.DeviceBlocks = BigEndianBitConverter.ToUInt32(header, 0x11) & 0x00FFFFFF; - tmpHdr.BytesPerBlock = BigEndianBitConverter.ToUInt16(header, 0x15); - - for(int i = 0; i < 0xD; i++) - if(tmpHdr.DeviceName[i] < 0x20) - return false; - - return (tmpHdr.BytesPerBlock & 0xFE00) == 0x200; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - imageHeader = new BluHeader {DeviceName = new byte[0x0D]}; - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - byte[] header = new byte[0x17]; - stream.Read(header, 0, 0x17); - Array.Copy(header, 0, imageHeader.DeviceName, 0, 0x0D); - imageHeader.DeviceType = BigEndianBitConverter.ToUInt32(header, 0x0C) & 0x00FFFFFF; - imageHeader.DeviceBlocks = BigEndianBitConverter.ToUInt32(header, 0x11) & 0x00FFFFFF; - imageHeader.BytesPerBlock = BigEndianBitConverter.ToUInt16(header, 0x15); - - DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.deviceName = \"{0}\"", - StringHandlers.CToString(imageHeader.DeviceName)); - DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.deviceType = {0}", imageHeader.DeviceType); - DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.deviceBlock = {0}", imageHeader.DeviceBlocks); - DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.bytesPerBlock = {0}", imageHeader.BytesPerBlock); - - for(int i = 0; i < 0xD; i++) - if(imageHeader.DeviceName[i] < 0x20) - return false; - - if((imageHeader.BytesPerBlock & 0xFE00) != 0x200) return false; - - stream.Seek(0, SeekOrigin.Begin); - header = new byte[imageHeader.BytesPerBlock]; - stream.Read(header, 0, imageHeader.BytesPerBlock); - - imageInfo.SectorSize = 0x200; - - imageInfo.Sectors = imageHeader.DeviceBlocks; - imageInfo.ImageSize = imageHeader.DeviceBlocks * imageHeader.BytesPerBlock; - bptag = imageHeader.BytesPerBlock - 0x200; - byte[] hdrTag = new byte[bptag]; - Array.Copy(header, 0x200, hdrTag, 0, bptag); - - switch(StringHandlers.CToString(imageHeader.DeviceName)) - { - case PROFILE_NAME: - imageInfo.MediaType = - imageInfo.Sectors == 0x2600 ? MediaType.AppleProfile : MediaType.GENERIC_HDD; - imageInfo.Cylinders = 152; - imageInfo.Heads = 4; - imageInfo.SectorsPerTrack = 16; - break; - case PROFILE10_NAME: - imageInfo.MediaType = - imageInfo.Sectors == 0x4C00 ? MediaType.AppleProfile : MediaType.GENERIC_HDD; - imageInfo.Cylinders = 304; - imageInfo.Heads = 4; - imageInfo.SectorsPerTrack = 16; - break; - case WIDGET_NAME: - imageInfo.MediaType = - imageInfo.Sectors == 0x4C00 ? MediaType.AppleWidget : MediaType.GENERIC_HDD; - imageInfo.Cylinders = 304; - imageInfo.Heads = 4; - imageInfo.SectorsPerTrack = 16; - break; - case PRIAM_NAME: - imageInfo.MediaType = - imageInfo.Sectors == 0x022C7C ? MediaType.PriamDataTower : MediaType.GENERIC_HDD; - // This values are invented... - imageInfo.Cylinders = 419; - imageInfo.Heads = 4; - imageInfo.SectorsPerTrack = 85; - break; - default: - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - break; - } - - imageInfo.Application = StringHandlers.CToString(hdrTag); - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - - bluImageFilter = imageFilter; - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - - if(bptag > 0) imageInfo.ReadableSectorTags.Add(SectorTagType.AppleSectorTag); - - DicConsole.VerboseWriteLine("BLU image contains a disk of type {0}", imageInfo.MediaType); - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - return ReadSectorsTag(sectorAddress, 1, tag); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream buffer = new MemoryStream(); - int seek = 0; - int read = 0x200; - int skip = bptag; - - Stream stream = bluImageFilter.GetDataForkStream(); - stream.Seek((long)((sectorAddress + 1) * imageHeader.BytesPerBlock), SeekOrigin.Begin); - - for(int i = 0; i < length; i++) - { - stream.Seek(seek, SeekOrigin.Current); - byte[] sector = new byte[read]; - stream.Read(sector, 0, read); - buffer.Write(sector, 0, read); - stream.Seek(skip, SeekOrigin.Current); - } - - return buffer.ToArray(); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - if(tag != SectorTagType.AppleSectorTag) - throw new FeatureUnsupportedImageException($"Tag {tag} not supported by image format"); - - if(bptag == 0) throw new FeatureNotPresentImageException("Disk image does not have tags"); - - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream buffer = new MemoryStream(); - int seek = 0x200; - int read = bptag; - int skip = 0; - - Stream stream = bluImageFilter.GetDataForkStream(); - stream.Seek((long)((sectorAddress + 1) * imageHeader.BytesPerBlock), SeekOrigin.Begin); - - for(int i = 0; i < length; i++) - { - stream.Seek(seek, SeekOrigin.Current); - byte[] sector = new byte[read]; - stream.Read(sector, 0, read); - buffer.Write(sector, 0, read); - stream.Seek(skip, SeekOrigin.Current); - } - - return buffer.ToArray(); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - return ReadSectorsLong(sectorAddress, 1); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageHeader.BytesPerBlock]; - Stream stream = bluImageFilter.GetDataForkStream(); - stream.Seek((long)((sectorAddress + 1) * imageHeader.BytesPerBlock), SeekOrigin.Begin); - stream.Read(buffer, 0, buffer.Length); - - return buffer; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - // TODO: Check tag checkums - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, - out List unknownLbas) - { - failingLbas = new List(); - unknownLbas = new List(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable SupportedSectorTags => new[] {SectorTagType.AppleSectorTag}; - public IEnumerable SupportedMediaTypes => - new[] - { - MediaType.AppleProfile, MediaType.AppleWidget, MediaType.PriamDataTower, MediaType.GENERIC_HDD, - MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, MediaType.CompactFlashType2, - MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable KnownExtensions => new[] {".blu"}; // Just invented - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors > 0xFFFFFF) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; - - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; - - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; - - byte[] oldTag; - byte[] newTag; - - switch(data.Length - 512) - { - // Sony tag, convert to Profile - case 12 when longSectorSize == 532: - oldTag = new byte[12]; - Array.Copy(data, 512, oldTag, 0, 12); - newTag = LisaTag.DecodeSonyTag(oldTag)?.ToProfile().GetBytes(); - break; - // Sony tag, convert to Priam - case 12 when longSectorSize == 536: - oldTag = new byte[12]; - Array.Copy(data, 512, oldTag, 0, 12); - newTag = LisaTag.DecodeSonyTag(oldTag)?.ToPriam().GetBytes(); - break; - // Profile tag, copy to Profile - case 20 when longSectorSize == 532: - newTag = new byte[20]; - Array.Copy(data, 512, newTag, 0, 20); - break; - // Profile tag, convert to Priam - case 20 when longSectorSize == 536: - oldTag = new byte[20]; - Array.Copy(data, 512, oldTag, 0, 20); - newTag = LisaTag.DecodeProfileTag(oldTag)?.ToPriam().GetBytes(); - break; - // Priam tag, convert to Profile - case 24 when longSectorSize == 532: - oldTag = new byte[24]; - Array.Copy(data, 512, oldTag, 0, 24); - newTag = LisaTag.DecodePriamTag(oldTag)?.ToProfile().GetBytes(); - break; - // Priam tag, copy to Priam - case 12 when longSectorSize == 536: - newTag = new byte[24]; - Array.Copy(data, 512, newTag, 0, 24); - break; - case 0: - newTag = null; - break; - default: - ErrorMessage = "Incorrect data size"; - return false; - } - - if(newTag == null) newTag = new byte[longSectorSize - 512]; - - writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); - writingStream.Write(data, 0, 512); - writingStream.Write(newTag, 0, newTag.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; - long givenSectorSize = data.Length / length; - - switch(givenSectorSize) - { - case 536: - case 532: - case 524: - case 512: break; - default: - ErrorMessage = "Incorrect data size"; - return false; - } - - for(uint i = 0; i < length; i++) - { - byte[] oldTag; - byte[] newTag; - - switch(givenSectorSize - 512) - { - // Sony tag, convert to Profile - case 12 when longSectorSize == 532: - oldTag = new byte[12]; - Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 12); - newTag = LisaTag.DecodeSonyTag(oldTag)?.ToProfile().GetBytes(); - break; - // Sony tag, convert to Priam - case 12 when longSectorSize == 536: - oldTag = new byte[12]; - Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 12); - newTag = LisaTag.DecodeSonyTag(oldTag)?.ToPriam().GetBytes(); - break; - // Profile tag, copy to Profile - case 20 when longSectorSize == 532: - newTag = new byte[20]; - Array.Copy(data, givenSectorSize * i + 512, newTag, 0, 20); - break; - // Profile tag, convert to Priam - case 20 when longSectorSize == 536: - oldTag = new byte[20]; - Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 20); - newTag = LisaTag.DecodeProfileTag(oldTag)?.ToPriam().GetBytes(); - break; - // Priam tag, convert to Profile - case 24 when longSectorSize == 532: - oldTag = new byte[24]; - Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 24); - newTag = LisaTag.DecodePriamTag(oldTag)?.ToProfile().GetBytes(); - break; - // Priam tag, copy to Priam - case 12 when longSectorSize == 536: - newTag = new byte[24]; - Array.Copy(data, givenSectorSize * i + 512, newTag, 0, 24); - break; - case 0: - newTag = null; - break; - default: - ErrorMessage = "Incorrect data size"; - return false; - } - - if(newTag == null) newTag = new byte[longSectorSize - 512]; - - writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); - writingStream.Write(data, (int)(givenSectorSize * i), 512); - writingStream.Write(newTag, 0, newTag.Length); - } - - ErrorMessage = ""; - return true; - } - - public bool SetTracks(List tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - byte[] markerTag = Encoding.UTF8.GetBytes("DiscImageChef " + Version.GetVersion()); - byte[] driveName; - byte[] driveType = new byte[3]; - byte[] driveBlocks = BigEndianBitConverter.GetBytes((uint)imageInfo.Sectors); - int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; - byte[] blockSize = BigEndianBitConverter.GetBytes((ushort)longSectorSize); - - switch(imageInfo.MediaType) - { - case MediaType.AppleProfile when imageInfo.Sectors == 0x4C00: - driveName = Encoding.ASCII.GetBytes(PROFILE10_NAME); - break; - case MediaType.AppleWidget when imageInfo.Sectors == 0x4C00: - driveType[1] = 0x01; - driveName = Encoding.ASCII.GetBytes(PROFILE10_NAME); - break; - case MediaType.PriamDataTower when imageInfo.Sectors == 0x22C7C: - driveType[1] = 0xFF; - driveName = Encoding.ASCII.GetBytes(PRIAM_NAME); - break; - default: - driveName = Encoding.ASCII.GetBytes(PROFILE_NAME); - break; - } - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(driveName, 0, driveName.Length >= 0xD ? 0xD : driveName.Length); - writingStream.Seek(0xD, SeekOrigin.Begin); - writingStream.Write(driveType, 0, 3); - writingStream.Seek(0x12, SeekOrigin.Begin); - writingStream.Write(driveBlocks, 1, 3); - writingStream.Seek(0x15, SeekOrigin.Begin); - writingStream.Write(blockSize, 1, 2); - writingStream.Seek(512, SeekOrigin.Begin); - writingStream.Write(markerTag, 0, - markerTag.Length >= longSectorSize - 512 ? longSectorSize - 512 : markerTag.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - struct BluHeader - { - public byte[] DeviceName; - public uint DeviceType; - public uint DeviceBlocks; - public ushort BytesPerBlock; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU/BLU.cs b/DiscImageChef.DiscImages/BLU/BLU.cs new file mode 100644 index 000000000..31115596d --- /dev/null +++ b/DiscImageChef.DiscImages/BLU/BLU.cs @@ -0,0 +1,84 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : BLU.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Basic Lisa Utility disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Blu : IWritableImage + { + IFilter bluImageFilter; + int bptag; + BluHeader imageHeader; + ImageInfo imageInfo; + FileStream writingStream; + + public Blu() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + + struct BluHeader + { + public byte[] DeviceName; + public uint DeviceType; + public uint DeviceBlocks; + public ushort BytesPerBlock; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU/Constants.cs b/DiscImageChef.DiscImages/BLU/Constants.cs new file mode 100644 index 000000000..ab11d2011 --- /dev/null +++ b/DiscImageChef.DiscImages/BLU/Constants.cs @@ -0,0 +1,42 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Basic Lisa Utility disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Blu + { + const string PROFILE_NAME = "PROFILE "; + const string PROFILE10_NAME = "PROFILE 10 "; + const string WIDGET_NAME = "WIDGET-10 "; + const string PRIAM_NAME = "PRIAMDTATOWER"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU/Identify.cs b/DiscImageChef.DiscImages/BLU/Identify.cs new file mode 100644 index 000000000..a488b0766 --- /dev/null +++ b/DiscImageChef.DiscImages/BLU/Identify.cs @@ -0,0 +1,66 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Basic Lisa Utility disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Blu + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 0x200) return false; + + byte[] header = new byte[0x17]; + stream.Read(header, 0, 0x17); + + BluHeader tmpHdr = new BluHeader {DeviceName = new byte[0x0D]}; + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + Array.Copy(header, 0, tmpHdr.DeviceName, 0, 0x0D); + tmpHdr.DeviceType = BigEndianBitConverter.ToUInt32(header, 0x0C) & 0x00FFFFFF; + tmpHdr.DeviceBlocks = BigEndianBitConverter.ToUInt32(header, 0x11) & 0x00FFFFFF; + tmpHdr.BytesPerBlock = BigEndianBitConverter.ToUInt16(header, 0x15); + + for(int i = 0; i < 0xD; i++) + if(tmpHdr.DeviceName[i] < 0x20) + return false; + + return (tmpHdr.BytesPerBlock & 0xFE00) == 0x200; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU/Properties.cs b/DiscImageChef.DiscImages/BLU/Properties.cs new file mode 100644 index 000000000..0ac726cbc --- /dev/null +++ b/DiscImageChef.DiscImages/BLU/Properties.cs @@ -0,0 +1,72 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Basic Lisa Utility disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Blu + { + public ImageInfo Info => imageInfo; + public string Name => "Basic Lisa Utility"; + public Guid Id => new Guid("A153E2F8-4235-432D-9A7F-20807B0BCD74"); + public string Format => "Basic Lisa Utility"; + public List Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable SupportedSectorTags => new[] {SectorTagType.AppleSectorTag}; + public IEnumerable SupportedMediaTypes => + new[] + { + MediaType.AppleProfile, MediaType.AppleWidget, MediaType.PriamDataTower, MediaType.GENERIC_HDD, + MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, MediaType.CompactFlashType2, + MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable KnownExtensions => new[] {".blu"}; // Just invented + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU/Read.cs b/DiscImageChef.DiscImages/BLU/Read.cs new file mode 100644 index 000000000..aba59da96 --- /dev/null +++ b/DiscImageChef.DiscImages/BLU/Read.cs @@ -0,0 +1,250 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Basic Lisa Utility disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Blu + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + imageHeader = new BluHeader {DeviceName = new byte[0x0D]}; + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + byte[] header = new byte[0x17]; + stream.Read(header, 0, 0x17); + Array.Copy(header, 0, imageHeader.DeviceName, 0, 0x0D); + imageHeader.DeviceType = BigEndianBitConverter.ToUInt32(header, 0x0C) & 0x00FFFFFF; + imageHeader.DeviceBlocks = BigEndianBitConverter.ToUInt32(header, 0x11) & 0x00FFFFFF; + imageHeader.BytesPerBlock = BigEndianBitConverter.ToUInt16(header, 0x15); + + DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.deviceName = \"{0}\"", + StringHandlers.CToString(imageHeader.DeviceName)); + DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.deviceType = {0}", imageHeader.DeviceType); + DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.deviceBlock = {0}", imageHeader.DeviceBlocks); + DicConsole.DebugWriteLine("BLU plugin", "ImageHeader.bytesPerBlock = {0}", imageHeader.BytesPerBlock); + + for(int i = 0; i < 0xD; i++) + if(imageHeader.DeviceName[i] < 0x20) + return false; + + if((imageHeader.BytesPerBlock & 0xFE00) != 0x200) return false; + + stream.Seek(0, SeekOrigin.Begin); + header = new byte[imageHeader.BytesPerBlock]; + stream.Read(header, 0, imageHeader.BytesPerBlock); + + imageInfo.SectorSize = 0x200; + + imageInfo.Sectors = imageHeader.DeviceBlocks; + imageInfo.ImageSize = imageHeader.DeviceBlocks * imageHeader.BytesPerBlock; + bptag = imageHeader.BytesPerBlock - 0x200; + byte[] hdrTag = new byte[bptag]; + Array.Copy(header, 0x200, hdrTag, 0, bptag); + + switch(StringHandlers.CToString(imageHeader.DeviceName)) + { + case PROFILE_NAME: + imageInfo.MediaType = + imageInfo.Sectors == 0x2600 ? MediaType.AppleProfile : MediaType.GENERIC_HDD; + imageInfo.Cylinders = 152; + imageInfo.Heads = 4; + imageInfo.SectorsPerTrack = 16; + break; + case PROFILE10_NAME: + imageInfo.MediaType = + imageInfo.Sectors == 0x4C00 ? MediaType.AppleProfile : MediaType.GENERIC_HDD; + imageInfo.Cylinders = 304; + imageInfo.Heads = 4; + imageInfo.SectorsPerTrack = 16; + break; + case WIDGET_NAME: + imageInfo.MediaType = + imageInfo.Sectors == 0x4C00 ? MediaType.AppleWidget : MediaType.GENERIC_HDD; + imageInfo.Cylinders = 304; + imageInfo.Heads = 4; + imageInfo.SectorsPerTrack = 16; + break; + case PRIAM_NAME: + imageInfo.MediaType = + imageInfo.Sectors == 0x022C7C ? MediaType.PriamDataTower : MediaType.GENERIC_HDD; + // This values are invented... + imageInfo.Cylinders = 419; + imageInfo.Heads = 4; + imageInfo.SectorsPerTrack = 85; + break; + default: + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + break; + } + + imageInfo.Application = StringHandlers.CToString(hdrTag); + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + + bluImageFilter = imageFilter; + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + + if(bptag > 0) imageInfo.ReadableSectorTags.Add(SectorTagType.AppleSectorTag); + + DicConsole.VerboseWriteLine("BLU image contains a disk of type {0}", imageInfo.MediaType); + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + return ReadSectorsTag(sectorAddress, 1, tag); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream buffer = new MemoryStream(); + int seek = 0; + int read = 0x200; + int skip = bptag; + + Stream stream = bluImageFilter.GetDataForkStream(); + stream.Seek((long)((sectorAddress + 1) * imageHeader.BytesPerBlock), SeekOrigin.Begin); + + for(int i = 0; i < length; i++) + { + stream.Seek(seek, SeekOrigin.Current); + byte[] sector = new byte[read]; + stream.Read(sector, 0, read); + buffer.Write(sector, 0, read); + stream.Seek(skip, SeekOrigin.Current); + } + + return buffer.ToArray(); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + if(tag != SectorTagType.AppleSectorTag) + throw new FeatureUnsupportedImageException($"Tag {tag} not supported by image format"); + + if(bptag == 0) throw new FeatureNotPresentImageException("Disk image does not have tags"); + + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream buffer = new MemoryStream(); + int seek = 0x200; + int read = bptag; + int skip = 0; + + Stream stream = bluImageFilter.GetDataForkStream(); + stream.Seek((long)((sectorAddress + 1) * imageHeader.BytesPerBlock), SeekOrigin.Begin); + + for(int i = 0; i < length; i++) + { + stream.Seek(seek, SeekOrigin.Current); + byte[] sector = new byte[read]; + stream.Read(sector, 0, read); + buffer.Write(sector, 0, read); + stream.Seek(skip, SeekOrigin.Current); + } + + return buffer.ToArray(); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + return ReadSectorsLong(sectorAddress, 1); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageHeader.BytesPerBlock]; + Stream stream = bluImageFilter.GetDataForkStream(); + stream.Seek((long)((sectorAddress + 1) * imageHeader.BytesPerBlock), SeekOrigin.Begin); + stream.Read(buffer, 0, buffer.Length); + + return buffer; + } + + // TODO: Check tag checkums + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU/Unsupported.cs b/DiscImageChef.DiscImages/BLU/Unsupported.cs new file mode 100644 index 000000000..8cbcc3b7e --- /dev/null +++ b/DiscImageChef.DiscImages/BLU/Unsupported.cs @@ -0,0 +1,108 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Basic Lisa Utility disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Blu + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, + out List unknownLbas) + { + failingLbas = new List(); + unknownLbas = new List(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BLU/Write.cs b/DiscImageChef.DiscImages/BLU/Write.cs new file mode 100644 index 000000000..df78fe365 --- /dev/null +++ b/DiscImageChef.DiscImages/BLU/Write.cs @@ -0,0 +1,404 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Basic Lisa Utility disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Decoders; +using Schemas; +using Version = DiscImageChef.CommonTypes.Interop.Version; + +namespace DiscImageChef.DiscImages +{ + public partial class Blu + { + public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors > 0xFFFFFF) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; + + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; + + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; + + byte[] oldTag; + byte[] newTag; + + switch(data.Length - 512) + { + // Sony tag, convert to Profile + case 12 when longSectorSize == 532: + oldTag = new byte[12]; + Array.Copy(data, 512, oldTag, 0, 12); + newTag = LisaTag.DecodeSonyTag(oldTag)?.ToProfile().GetBytes(); + break; + // Sony tag, convert to Priam + case 12 when longSectorSize == 536: + oldTag = new byte[12]; + Array.Copy(data, 512, oldTag, 0, 12); + newTag = LisaTag.DecodeSonyTag(oldTag)?.ToPriam().GetBytes(); + break; + // Profile tag, copy to Profile + case 20 when longSectorSize == 532: + newTag = new byte[20]; + Array.Copy(data, 512, newTag, 0, 20); + break; + // Profile tag, convert to Priam + case 20 when longSectorSize == 536: + oldTag = new byte[20]; + Array.Copy(data, 512, oldTag, 0, 20); + newTag = LisaTag.DecodeProfileTag(oldTag)?.ToPriam().GetBytes(); + break; + // Priam tag, convert to Profile + case 24 when longSectorSize == 532: + oldTag = new byte[24]; + Array.Copy(data, 512, oldTag, 0, 24); + newTag = LisaTag.DecodePriamTag(oldTag)?.ToProfile().GetBytes(); + break; + // Priam tag, copy to Priam + case 12 when longSectorSize == 536: + newTag = new byte[24]; + Array.Copy(data, 512, newTag, 0, 24); + break; + case 0: + newTag = null; + break; + default: + ErrorMessage = "Incorrect data size"; + return false; + } + + if(newTag == null) newTag = new byte[longSectorSize - 512]; + + writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); + writingStream.Write(data, 0, 512); + writingStream.Write(newTag, 0, newTag.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; + long givenSectorSize = data.Length / length; + + switch(givenSectorSize) + { + case 536: + case 532: + case 524: + case 512: break; + default: + ErrorMessage = "Incorrect data size"; + return false; + } + + for(uint i = 0; i < length; i++) + { + byte[] oldTag; + byte[] newTag; + + switch(givenSectorSize - 512) + { + // Sony tag, convert to Profile + case 12 when longSectorSize == 532: + oldTag = new byte[12]; + Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 12); + newTag = LisaTag.DecodeSonyTag(oldTag)?.ToProfile().GetBytes(); + break; + // Sony tag, convert to Priam + case 12 when longSectorSize == 536: + oldTag = new byte[12]; + Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 12); + newTag = LisaTag.DecodeSonyTag(oldTag)?.ToPriam().GetBytes(); + break; + // Profile tag, copy to Profile + case 20 when longSectorSize == 532: + newTag = new byte[20]; + Array.Copy(data, givenSectorSize * i + 512, newTag, 0, 20); + break; + // Profile tag, convert to Priam + case 20 when longSectorSize == 536: + oldTag = new byte[20]; + Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 20); + newTag = LisaTag.DecodeProfileTag(oldTag)?.ToPriam().GetBytes(); + break; + // Priam tag, convert to Profile + case 24 when longSectorSize == 532: + oldTag = new byte[24]; + Array.Copy(data, givenSectorSize * i + 512, oldTag, 0, 24); + newTag = LisaTag.DecodePriamTag(oldTag)?.ToProfile().GetBytes(); + break; + // Priam tag, copy to Priam + case 12 when longSectorSize == 536: + newTag = new byte[24]; + Array.Copy(data, givenSectorSize * i + 512, newTag, 0, 24); + break; + case 0: + newTag = null; + break; + default: + ErrorMessage = "Incorrect data size"; + return false; + } + + if(newTag == null) newTag = new byte[longSectorSize - 512]; + + writingStream.Seek(longSectorSize + (long)sectorAddress * longSectorSize, SeekOrigin.Begin); + writingStream.Write(data, (int)(givenSectorSize * i), 512); + writingStream.Write(newTag, 0, newTag.Length); + } + + ErrorMessage = ""; + return true; + } + + public bool SetTracks(List tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + byte[] markerTag = Encoding.UTF8.GetBytes("DiscImageChef " + Version.GetVersion()); + byte[] driveName; + byte[] driveType = new byte[3]; + byte[] driveBlocks = BigEndianBitConverter.GetBytes((uint)imageInfo.Sectors); + int longSectorSize = imageInfo.MediaType == MediaType.PriamDataTower ? 536 : 532; + byte[] blockSize = BigEndianBitConverter.GetBytes((ushort)longSectorSize); + + switch(imageInfo.MediaType) + { + case MediaType.AppleProfile when imageInfo.Sectors == 0x4C00: + driveName = Encoding.ASCII.GetBytes(PROFILE10_NAME); + break; + case MediaType.AppleWidget when imageInfo.Sectors == 0x4C00: + driveType[1] = 0x01; + driveName = Encoding.ASCII.GetBytes(PROFILE10_NAME); + break; + case MediaType.PriamDataTower when imageInfo.Sectors == 0x22C7C: + driveType[1] = 0xFF; + driveName = Encoding.ASCII.GetBytes(PRIAM_NAME); + break; + default: + driveName = Encoding.ASCII.GetBytes(PROFILE_NAME); + break; + } + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(driveName, 0, driveName.Length >= 0xD ? 0xD : driveName.Length); + writingStream.Seek(0xD, SeekOrigin.Begin); + writingStream.Write(driveType, 0, 3); + writingStream.Seek(0x12, SeekOrigin.Begin); + writingStream.Write(driveBlocks, 1, 3); + writingStream.Seek(0x15, SeekOrigin.Begin); + writingStream.Write(blockSize, 1, 2); + writingStream.Seek(512, SeekOrigin.Begin); + writingStream.Write(markerTag, 0, + markerTag.Length >= longSectorSize - 512 ? longSectorSize - 512 : markerTag.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite4/BlindWrite4.cs b/DiscImageChef.DiscImages/BlindWrite4/BlindWrite4.cs new file mode 100644 index 000000000..0ca73271e --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite4/BlindWrite4.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : BlindWrite4.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages BlindWrite 4 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Too many unknowns, plus a completely unknown footer, to make this writable + public partial class BlindWrite4 : IMediaImage + { + List bwTracks; + IFilter dataFilter, subFilter; + + Bw4Header header; + ImageInfo imageInfo; + Stream imageStream; + Dictionary offsetmap; + Dictionary trackFlags; + + public BlindWrite4() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = true, + HasSessions = true, + Version = null, + ApplicationVersion = null, + MediaTitle = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite4/Constants.cs b/DiscImageChef.DiscImages/BlindWrite4/Constants.cs new file mode 100644 index 000000000..230cf6351 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite4/Constants.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for BlindWrite 4 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite4 + { + /// "BLINDWRITE TOC FILE" + readonly byte[] bw4Signature = + { + 0x42, 0x4C, 0x49, 0x4E, 0x44, 0x57, 0x52, 0x49, 0x54, 0x45, 0x20, 0x54, 0x4F, 0x43, 0x20, 0x46, 0x49, 0x4C, + 0x45 + }; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite4/Enums.cs b/DiscImageChef.DiscImages/BlindWrite4/Enums.cs new file mode 100644 index 000000000..52c9fd41d --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite4/Enums.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for BlindWrite 4 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite4 + { + enum Bw4TrackType : byte + { + Audio = 0, + Mode1 = 1, + Mode2 = 2 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite4/Identify.cs b/DiscImageChef.DiscImages/BlindWrite4/Identify.cs new file mode 100644 index 000000000..6bfe72ca2 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite4/Identify.cs @@ -0,0 +1,53 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies BlindWrite 4 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite4 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < 19) return false; + + byte[] signature = new byte[19]; + stream.Read(signature, 0, 19); + + return bw4Signature.SequenceEqual(signature); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite4/Properties.cs b/DiscImageChef.DiscImages/BlindWrite4/Properties.cs new file mode 100644 index 000000000..3090df49c --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite4/Properties.cs @@ -0,0 +1,58 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for BlindWrite 4 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite4 + { + public ImageInfo Info => imageInfo; + + public string Name => "BlindWrite 4"; + public Guid Id => new Guid("664568B2-15D4-4E64-8A7A-20BDA8B8386F"); + + public string Format => "BlindWrite 4 TOC file"; + + public List Partitions { get; set; } + + public List Tracks { get; set; } + + public List Sessions { get; set; } + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite4.cs b/DiscImageChef.DiscImages/BlindWrite4/Read.cs similarity index 89% rename from DiscImageChef.DiscImages/BlindWrite4.cs rename to DiscImageChef.DiscImages/BlindWrite4/Read.cs index 4aa0452c4..cc0788d47 100644 --- a/DiscImageChef.DiscImages/BlindWrite4.cs +++ b/DiscImageChef.DiscImages/BlindWrite4/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : BlindWrite4.cs +// Filename : Read.cs // Author(s) : Natalia Portillo // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages BlindWrite 4 disc images. +// Reads BlindWrite 4 disc images. // // --[ License ] -------------------------------------------------------------- // @@ -35,7 +35,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using DiscImageChef.Checksums; using DiscImageChef.CommonTypes; @@ -44,79 +43,11 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // TODO: Too many unknowns, plus a completely unknown footer, to make this writable - public class BlindWrite4 : IMediaImage + public partial class BlindWrite4 { - /// "BLINDWRITE TOC FILE" - readonly byte[] bw4Signature = - { - 0x42, 0x4C, 0x49, 0x4E, 0x44, 0x57, 0x52, 0x49, 0x54, 0x45, 0x20, 0x54, 0x4F, 0x43, 0x20, 0x46, 0x49, 0x4C, - 0x45 - }; - List bwTracks; - IFilter dataFilter, subFilter; - - Bw4Header header; - ImageInfo imageInfo; - Stream imageStream; - Dictionary offsetmap; - Dictionary trackFlags; - - public BlindWrite4() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = true, - HasSessions = true, - Version = null, - ApplicationVersion = null, - MediaTitle = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "BlindWrite 4"; - public Guid Id => new Guid("664568B2-15D4-4E64-8A7A-20BDA8B8386F"); - - public string Format => "BlindWrite 4 TOC file"; - - public List Partitions { get; set; } - - public List Tracks { get; set; } - - public List Sessions { get; set; } - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < 19) return false; - - byte[] signature = new byte[19]; - stream.Read(signature, 0, 19); - - return bw4Signature.SequenceEqual(signature); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -1197,126 +1128,5 @@ namespace DiscImageChef.DiscImages { return null; } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - struct Bw4Header - { - public byte[] Signature; - public uint Unknown1; - public ulong Timestamp; - public uint VolumeIdLength; - public byte[] VolumeIdBytes; - public uint SysIdLength; - public byte[] SysIdBytes; - public uint CommentsLength; - public byte[] CommentsBytes; - public uint TrackDescriptors; - public uint DataFileLength; - public byte[] DataFileBytes; - public uint SubchannelFileLength; - public byte[] SubchannelFileBytes; - public uint Unknown2; - public byte Unknown3; - public byte[] Unknown4; - - // On memory only - #pragma warning disable 649 - public string VolumeIdentifier; - public string SystemIdentifier; - public string Comments; - public IFilter DataFilter; - public IFilter SubchannelFilter; - public string DataFile; - public string SubchannelFile; - #pragma warning restore 649 - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct Bw4TrackDescriptor - { - public uint filenameLen; - public byte[] filenameBytes; - public uint offset; - public byte subchannel; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] unknown1; - public uint unknown2; - public byte unknown3; - public byte session; - public byte unknown4; - public byte adrCtl; - public byte unknown5; - public Bw4TrackType trackMode; - public byte unknown6; - public byte point; - public uint unknown7; - public uint unknown8; - public uint unknown9; - public uint unknown10; - public ushort unknown11; - public uint lastSector; - public byte unknown12; - public int pregap; - public int startSector; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] unknown13; - public uint titleLen; - public byte[] titleBytes; - public uint performerLen; - public byte[] performerBytes; - public uint unkStrLen1; - public byte[] unkStrBytes1; - public uint unkStrLen2; - public byte[] unkStrBytes2; - public uint unkStrLen3; - public byte[] unkStrBytes3; - public uint unkStrLen4; - public byte[] unkStrBytes4; - public uint discIdLen; - public byte[] discIdBytes; - public uint unkStrLen5; - public byte[] unkStrBytes5; - public uint unkStrLen6; - public byte[] unkStrBytes6; - public uint unkStrLen7; - public byte[] unkStrBytes7; - public uint unkStrLen8; - public byte[] unkStrBytes8; - public uint unkStrLen9; - public byte[] unkStrBytes9; - public uint unkStrLen10; - public byte[] unkStrBytes10; - public uint unkStrLen11; - public byte[] unkStrBytes11; - public uint isrcLen; - public byte[] isrcBytes; - - // On memory only - public string filename; - public string title; - public string performer; - public string unkString1; - public string unkString2; - public string unkString3; - public string unkString4; - public string discId; - public string unkString5; - public string unkString6; - public string unkString7; - public string unkString8; - public string unkString9; - public string unkString10; - public string unkString11; - public string isrcUpc; - } - - enum Bw4TrackType : byte - { - Audio = 0, - Mode1 = 1, - Mode2 = 2 - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite4/Structs.cs b/DiscImageChef.DiscImages/BlindWrite4/Structs.cs new file mode 100644 index 000000000..1831761f8 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite4/Structs.cs @@ -0,0 +1,151 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for BlindWrite 4 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite4 + { + struct Bw4Header + { + public byte[] Signature; + public uint Unknown1; + public ulong Timestamp; + public uint VolumeIdLength; + public byte[] VolumeIdBytes; + public uint SysIdLength; + public byte[] SysIdBytes; + public uint CommentsLength; + public byte[] CommentsBytes; + public uint TrackDescriptors; + public uint DataFileLength; + public byte[] DataFileBytes; + public uint SubchannelFileLength; + public byte[] SubchannelFileBytes; + public uint Unknown2; + public byte Unknown3; + public byte[] Unknown4; + + // On memory only + #pragma warning disable 649 + public string VolumeIdentifier; + public string SystemIdentifier; + public string Comments; + public IFilter DataFilter; + public IFilter SubchannelFilter; + public string DataFile; + public string SubchannelFile; + #pragma warning restore 649 + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Bw4TrackDescriptor + { + public uint filenameLen; + public byte[] filenameBytes; + public uint offset; + public byte subchannel; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] unknown1; + public uint unknown2; + public byte unknown3; + public byte session; + public byte unknown4; + public byte adrCtl; + public byte unknown5; + public Bw4TrackType trackMode; + public byte unknown6; + public byte point; + public uint unknown7; + public uint unknown8; + public uint unknown9; + public uint unknown10; + public ushort unknown11; + public uint lastSector; + public byte unknown12; + public int pregap; + public int startSector; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] unknown13; + public uint titleLen; + public byte[] titleBytes; + public uint performerLen; + public byte[] performerBytes; + public uint unkStrLen1; + public byte[] unkStrBytes1; + public uint unkStrLen2; + public byte[] unkStrBytes2; + public uint unkStrLen3; + public byte[] unkStrBytes3; + public uint unkStrLen4; + public byte[] unkStrBytes4; + public uint discIdLen; + public byte[] discIdBytes; + public uint unkStrLen5; + public byte[] unkStrBytes5; + public uint unkStrLen6; + public byte[] unkStrBytes6; + public uint unkStrLen7; + public byte[] unkStrBytes7; + public uint unkStrLen8; + public byte[] unkStrBytes8; + public uint unkStrLen9; + public byte[] unkStrBytes9; + public uint unkStrLen10; + public byte[] unkStrBytes10; + public uint unkStrLen11; + public byte[] unkStrBytes11; + public uint isrcLen; + public byte[] isrcBytes; + + // On memory only + public string filename; + public string title; + public string performer; + public string unkString1; + public string unkString2; + public string unkString3; + public string unkString4; + public string discId; + public string unkString5; + public string unkString6; + public string unkString7; + public string unkString8; + public string unkString9; + public string unkString10; + public string unkString11; + public string isrcUpc; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5/BlindWrite5.cs b/DiscImageChef.DiscImages/BlindWrite5/BlindWrite5.cs new file mode 100644 index 000000000..fc510fe30 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite5/BlindWrite5.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : BlindWrite5.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages BlindWrite 5 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Too many unknowns to make this writable + public partial class BlindWrite5 : IMediaImage + { + byte[] atip; + byte[] bca; + List bwSessions; + byte[] cdtext; + List dataFiles; + string dataPath; + byte[] discInformation; + byte[] dmi; + byte[] dpm; + List filePaths; + byte[] fullToc; + + Bw5Header header; + ImageInfo imageInfo; + Stream imageStream; + byte[] mode2A; + Dictionary offsetmap; + byte[] pfi; + byte[] pma; + Dictionary trackFlags; + byte[] unkBlock; + + public BlindWrite5() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = true, + HasSessions = true, + Version = null, + ApplicationVersion = null, + MediaTitle = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5/Constants.cs b/DiscImageChef.DiscImages/BlindWrite5/Constants.cs new file mode 100644 index 000000000..5a77d3191 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite5/Constants.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for BlindWrite 5 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite5 + { + /// "BWT5 STREAM FOOT" + readonly byte[] bw5Footer = + {0x42, 0x57, 0x54, 0x35, 0x20, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4D, 0x20, 0x46, 0x4F, 0x4F, 0x54}; + /// "BWT5 STREAM SIGN" + readonly byte[] bw5Signature = + {0x42, 0x57, 0x54, 0x35, 0x20, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4D, 0x20, 0x53, 0x49, 0x47, 0x4E}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5/Enums.cs b/DiscImageChef.DiscImages/BlindWrite5/Enums.cs new file mode 100644 index 000000000..886849fe7 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite5/Enums.cs @@ -0,0 +1,55 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for BlindWrite 5 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite5 + { + enum Bw5TrackType : byte + { + NotData = 0, + Audio = 1, + Mode1 = 2, + Mode2 = 3, + Mode2F1 = 4, + Mode2F2 = 5, + Dvd = 6 + } + + enum Bw5TrackSubchannel : byte + { + None = 0, + Q16 = 2, + Linear = 4 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5/Helpers.cs b/DiscImageChef.DiscImages/BlindWrite5/Helpers.cs new file mode 100644 index 000000000..78c0b118f --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite5/Helpers.cs @@ -0,0 +1,97 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for BlindWrite 5 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.Decoders.SCSI.MMC; + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite5 + { + static TrackType BlindWriteTrackTypeToTrackType(Bw5TrackType trackType) + { + switch(trackType) + { + case Bw5TrackType.Mode1: return TrackType.CdMode1; + case Bw5TrackType.Mode2F1: return TrackType.CdMode2Form1; + case Bw5TrackType.Mode2F2: return TrackType.CdMode2Form2; + case Bw5TrackType.Mode2: return TrackType.CdMode2Formless; + case Bw5TrackType.Audio: return TrackType.Audio; + default: return TrackType.Data; + } + } + + static MediaType BlindWriteProfileToMediaType(ProfileNumber profile) + { + switch(profile) + { + case ProfileNumber.BDRE: return MediaType.BDRE; + case ProfileNumber.BDROM: return MediaType.BDROM; + case ProfileNumber.BDRRdm: + case ProfileNumber.BDRSeq: return MediaType.BDR; + case ProfileNumber.CDR: + case ProfileNumber.HDBURNR: return MediaType.CDR; + case ProfileNumber.CDROM: + case ProfileNumber.HDBURNROM: return MediaType.CDROM; + case ProfileNumber.CDRW: + case ProfileNumber.HDBURNRW: return MediaType.CDRW; + case ProfileNumber.DDCDR: return MediaType.DDCDR; + case ProfileNumber.DDCDROM: return MediaType.DDCD; + case ProfileNumber.DDCDRW: return MediaType.DDCDRW; + case ProfileNumber.DVDDownload: return MediaType.DVDDownload; + case ProfileNumber.DVDRAM: return MediaType.DVDRAM; + case ProfileNumber.DVDRDLJump: + case ProfileNumber.DVDRDLSeq: return MediaType.DVDRDL; + case ProfileNumber.DVDRDLPlus: return MediaType.DVDPRDL; + case ProfileNumber.DVDROM: return MediaType.DVDROM; + case ProfileNumber.DVDRPlus: return MediaType.DVDPR; + case ProfileNumber.DVDRSeq: return MediaType.DVDR; + case ProfileNumber.DVDRWDL: return MediaType.DVDRWDL; + case ProfileNumber.DVDRWDLPlus: return MediaType.DVDPRWDL; + case ProfileNumber.DVDRWPlus: return MediaType.DVDPRW; + case ProfileNumber.DVDRWRes: + case ProfileNumber.DVDRWSeq: return MediaType.DVDRW; + case ProfileNumber.HDDVDR: return MediaType.HDDVDR; + case ProfileNumber.HDDVDRAM: return MediaType.HDDVDRAM; + case ProfileNumber.HDDVDRDL: return MediaType.HDDVDRDL; + case ProfileNumber.HDDVDROM: return MediaType.HDDVDROM; + case ProfileNumber.HDDVDRW: return MediaType.HDDVDRW; + case ProfileNumber.HDDVDRWDL: return MediaType.HDDVDRWDL; + case ProfileNumber.ASMO: + case ProfileNumber.MOErasable: return MediaType.UnknownMO; + case ProfileNumber.NonRemovable: return MediaType.GENERIC_HDD; + default: return MediaType.CD; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5/Identify.cs b/DiscImageChef.DiscImages/BlindWrite5/Identify.cs new file mode 100644 index 000000000..cbe8d51e8 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite5/Identify.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies BlindWrite 5 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite5 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < 276) return false; + + byte[] signature = new byte[16]; + stream.Read(signature, 0, 16); + + byte[] footer = new byte[16]; + stream.Seek(-16, SeekOrigin.End); + stream.Read(footer, 0, 16); + + return bw5Signature.SequenceEqual(signature) && bw5Footer.SequenceEqual(footer); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5/Properties.cs b/DiscImageChef.DiscImages/BlindWrite5/Properties.cs new file mode 100644 index 000000000..5f4d314b2 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite5/Properties.cs @@ -0,0 +1,59 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for BlindWrite 5 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite5 + { + public ImageInfo Info => imageInfo; + + public string Name => "BlindWrite 5"; + public Guid Id => new Guid("9CB7A381-0509-4F9F-B801-3F65434BC3EE"); + + public string Format => "BlindWrite 5 TOC file"; + + public List Partitions { get; private set; } + + public List Tracks { get; private set; } + + public List Sessions { get; private set; } + + public List DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5.cs b/DiscImageChef.DiscImages/BlindWrite5/Read.cs similarity index 87% rename from DiscImageChef.DiscImages/BlindWrite5.cs rename to DiscImageChef.DiscImages/BlindWrite5/Read.cs index 937e10095..ce6e49d0e 100644 --- a/DiscImageChef.DiscImages/BlindWrite5.cs +++ b/DiscImageChef.DiscImages/BlindWrite5/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : BlindWrite5.cs +// Filename : Read.cs // Author(s) : Natalia Portillo // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages BlindWrite 5 disc images. +// Reads BlindWrite 5 disc images. // // --[ License ] -------------------------------------------------------------- // @@ -48,98 +48,13 @@ using DiscImageChef.Decoders.CD; using DiscImageChef.Decoders.DVD; using DiscImageChef.Decoders.SCSI; using DiscImageChef.Decoders.SCSI.MMC; -using DiscImageChef.Filters; -using Schemas; using DMI = DiscImageChef.Decoders.Xbox.DMI; using Session = DiscImageChef.CommonTypes.Structs.Session; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // TODO: Too many unknowns to make this writable - public class BlindWrite5 : IMediaImage + public partial class BlindWrite5 { - /// "BWT5 STREAM FOOT" - readonly byte[] bw5Footer = - {0x42, 0x57, 0x54, 0x35, 0x20, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4D, 0x20, 0x46, 0x4F, 0x4F, 0x54}; - /// "BWT5 STREAM SIGN" - readonly byte[] bw5Signature = - {0x42, 0x57, 0x54, 0x35, 0x20, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4D, 0x20, 0x53, 0x49, 0x47, 0x4E}; - byte[] atip; - byte[] bca; - List bwSessions; - byte[] cdtext; - List dataFiles; - string dataPath; - byte[] discInformation; - byte[] dmi; - byte[] dpm; - List filePaths; - byte[] fullToc; - - Bw5Header header; - ImageInfo imageInfo; - Stream imageStream; - byte[] mode2A; - Dictionary offsetmap; - byte[] pfi; - byte[] pma; - Dictionary trackFlags; - byte[] unkBlock; - - public BlindWrite5() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = true, - HasSessions = true, - Version = null, - ApplicationVersion = null, - MediaTitle = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "BlindWrite 5"; - public Guid Id => new Guid("9CB7A381-0509-4F9F-B801-3F65434BC3EE"); - - public string Format => "BlindWrite 5 TOC file"; - - public List Partitions { get; private set; } - - public List Tracks { get; private set; } - - public List Sessions { get; private set; } - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < 276) return false; - - byte[] signature = new byte[16]; - stream.Read(signature, 0, 16); - - byte[] footer = new byte[16]; - stream.Seek(-16, SeekOrigin.End); - stream.Read(footer, 0, 16); - - return bw5Signature.SequenceEqual(signature) && bw5Footer.SequenceEqual(footer); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -1761,203 +1676,5 @@ namespace DiscImageChef.DiscImages { return null; } - - public List DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - static TrackType BlindWriteTrackTypeToTrackType(Bw5TrackType trackType) - { - switch(trackType) - { - case Bw5TrackType.Mode1: return TrackType.CdMode1; - case Bw5TrackType.Mode2F1: return TrackType.CdMode2Form1; - case Bw5TrackType.Mode2F2: return TrackType.CdMode2Form2; - case Bw5TrackType.Mode2: return TrackType.CdMode2Formless; - case Bw5TrackType.Audio: return TrackType.Audio; - default: return TrackType.Data; - } - } - - static MediaType BlindWriteProfileToMediaType(ProfileNumber profile) - { - switch(profile) - { - case ProfileNumber.BDRE: return MediaType.BDRE; - case ProfileNumber.BDROM: return MediaType.BDROM; - case ProfileNumber.BDRRdm: - case ProfileNumber.BDRSeq: return MediaType.BDR; - case ProfileNumber.CDR: - case ProfileNumber.HDBURNR: return MediaType.CDR; - case ProfileNumber.CDROM: - case ProfileNumber.HDBURNROM: return MediaType.CDROM; - case ProfileNumber.CDRW: - case ProfileNumber.HDBURNRW: return MediaType.CDRW; - case ProfileNumber.DDCDR: return MediaType.DDCDR; - case ProfileNumber.DDCDROM: return MediaType.DDCD; - case ProfileNumber.DDCDRW: return MediaType.DDCDRW; - case ProfileNumber.DVDDownload: return MediaType.DVDDownload; - case ProfileNumber.DVDRAM: return MediaType.DVDRAM; - case ProfileNumber.DVDRDLJump: - case ProfileNumber.DVDRDLSeq: return MediaType.DVDRDL; - case ProfileNumber.DVDRDLPlus: return MediaType.DVDPRDL; - case ProfileNumber.DVDROM: return MediaType.DVDROM; - case ProfileNumber.DVDRPlus: return MediaType.DVDPR; - case ProfileNumber.DVDRSeq: return MediaType.DVDR; - case ProfileNumber.DVDRWDL: return MediaType.DVDRWDL; - case ProfileNumber.DVDRWDLPlus: return MediaType.DVDPRWDL; - case ProfileNumber.DVDRWPlus: return MediaType.DVDPRW; - case ProfileNumber.DVDRWRes: - case ProfileNumber.DVDRWSeq: return MediaType.DVDRW; - case ProfileNumber.HDDVDR: return MediaType.HDDVDR; - case ProfileNumber.HDDVDRAM: return MediaType.HDDVDRAM; - case ProfileNumber.HDDVDRDL: return MediaType.HDDVDRDL; - case ProfileNumber.HDDVDROM: return MediaType.HDDVDROM; - case ProfileNumber.HDDVDRW: return MediaType.HDDVDRW; - case ProfileNumber.HDDVDRWDL: return MediaType.HDDVDRWDL; - case ProfileNumber.ASMO: - case ProfileNumber.MOErasable: return MediaType.UnknownMO; - case ProfileNumber.NonRemovable: return MediaType.GENERIC_HDD; - default: return MediaType.CD; - } - } - - enum Bw5TrackType : byte - { - NotData = 0, - Audio = 1, - Mode1 = 2, - Mode2 = 3, - Mode2F1 = 4, - Mode2F2 = 5, - Dvd = 6 - } - - enum Bw5TrackSubchannel : byte - { - None = 0, - Q16 = 2, - Linear = 4 - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct Bw5Header - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] signature; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public uint[] unknown1; - public ProfileNumber profile; - public ushort sessions; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public uint[] unknown2; - [MarshalAs(UnmanagedType.U1, SizeConst = 3)] - public bool mcnIsValid; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - public byte[] mcn; - public ushort unknown3; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public uint[] unknown4; - public ushort pmaLen; - public ushort atipLen; - public ushort cdtLen; - public ushort cdInfoLen; - public uint bcaLen; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public uint[] unknown5; - public uint dvdStrLen; - public uint dvdInfoLen; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] unknown6; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] manufacturer; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] product; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public byte[] revision; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] vendor; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] volumeId; - public uint mode2ALen; - public uint unkBlkLen; - public uint dataLen; - public uint sessionsLen; - public uint dpmLen; - } - - struct Bw5DataFile - { - public uint Type; - public uint Length; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public uint[] Unknown1; - public uint Offset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] Unknown2; - public int StartLba; - public int Sectors; - public uint FilenameLen; - public byte[] FilenameBytes; - public uint Unknown3; - - public string Filename; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct Bw5TrackDescriptor - { - public Bw5TrackType type; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] unknown1; - public uint unknown2; - public Bw5TrackSubchannel subchannel; - public byte unknown3; - public byte ctl; - public byte adr; - public byte point; - public byte tno; - public byte min; - public byte sec; - public byte frame; - public byte zero; - public byte pmin; - public byte psec; - public byte pframe; - public byte unknown5; - public uint pregap; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public uint[] unknown6; - public int startLba; - public int sectors; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] unknown7; - public uint session; - public ushort unknown8; - // Seems to be only on non DVD track descriptors - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] unknown9; - } - - struct Bw5SessionDescriptor - { - public ushort Sequence; - public byte Entries; - public byte Unknown; - public int Start; - public int End; - public ushort FirstTrack; - public ushort LastTrack; - public Bw5TrackDescriptor[] Tracks; - } - - struct DataFileCharacteristics - { - public IFilter FileFilter; - public string FilePath; - public TrackSubchannelType Subchannel; - public long SectorSize; - public int StartLba; - public int Sectors; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/BlindWrite5/Structs.cs b/DiscImageChef.DiscImages/BlindWrite5/Structs.cs new file mode 100644 index 000000000..870ec8881 --- /dev/null +++ b/DiscImageChef.DiscImages/BlindWrite5/Structs.cs @@ -0,0 +1,166 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for BlindWrite 5 disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Decoders.SCSI.MMC; + +namespace DiscImageChef.DiscImages +{ + public partial class BlindWrite5 + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Bw5Header + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] signature; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public uint[] unknown1; + public ProfileNumber profile; + public ushort sessions; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public uint[] unknown2; + [MarshalAs(UnmanagedType.U1, SizeConst = 3)] + public bool mcnIsValid; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public byte[] mcn; + public ushort unknown3; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] unknown4; + public ushort pmaLen; + public ushort atipLen; + public ushort cdtLen; + public ushort cdInfoLen; + public uint bcaLen; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public uint[] unknown5; + public uint dvdStrLen; + public uint dvdInfoLen; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] unknown6; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] manufacturer; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] product; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] revision; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] vendor; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] volumeId; + public uint mode2ALen; + public uint unkBlkLen; + public uint dataLen; + public uint sessionsLen; + public uint dpmLen; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Bw5DataFile + { + public uint Type; + public uint Length; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public uint[] Unknown1; + public uint Offset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] Unknown2; + public int StartLba; + public int Sectors; + public uint FilenameLen; + public byte[] FilenameBytes; + public uint Unknown3; + + public string Filename; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Bw5TrackDescriptor + { + public Bw5TrackType type; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] unknown1; + public uint unknown2; + public Bw5TrackSubchannel subchannel; + public byte unknown3; + public byte ctl; + public byte adr; + public byte point; + public byte tno; + public byte min; + public byte sec; + public byte frame; + public byte zero; + public byte pmin; + public byte psec; + public byte pframe; + public byte unknown5; + public uint pregap; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] unknown6; + public int startLba; + public int sectors; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] unknown7; + public uint session; + public ushort unknown8; + // Seems to be only on non DVD track descriptors + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] unknown9; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Bw5SessionDescriptor + { + public ushort Sequence; + public byte Entries; + public byte Unknown; + public int Start; + public int End; + public ushort FirstTrack; + public ushort LastTrack; + public Bw5TrackDescriptor[] Tracks; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DataFileCharacteristics + { + public IFilter FileFilter; + public string FilePath; + public TrackSubchannelType Subchannel; + public long SectorSize; + public int StartLba; + public int Sectors; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/CDRDAO.cs b/DiscImageChef.DiscImages/CDRDAO/CDRDAO.cs new file mode 100644 index 000000000..d70c6fe86 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/CDRDAO.cs @@ -0,0 +1,84 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CDRDAO.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Doesn't support compositing from several files + // TODO: Doesn't support silences that are not in files + public partial class Cdrdao : IWritableImage + { + IFilter cdrdaoFilter; + StreamWriter descriptorStream; + CdrdaoDisc discimage; + ImageInfo imageInfo; + Stream imageStream; + /// Dictionary, index is track #, value is TrackFile + Dictionary offsetmap; + bool separateTracksWriting; + StreamReader tocStream; + Dictionary trackFlags; + Dictionary trackIsrcs; + string writingBaseName; + Dictionary writingStreams; + List writingTracks; + + public Cdrdao() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = true, + HasSessions = true, + Version = null, + ApplicationVersion = null, + MediaTitle = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/Constants.cs b/DiscImageChef.DiscImages/CDRDAO/Constants.cs new file mode 100644 index 000000000..659e68e80 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/Constants.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Cdrdao + { + /// Audio track, 2352 bytes/sector + const string CDRDAO_TRACK_TYPE_AUDIO = "AUDIO"; + /// Mode 1 track, cooked, 2048 bytes/sector + const string CDRDAO_TRACK_TYPE_MODE1 = "MODE1"; + /// Mode 1 track, raw, 2352 bytes/sector + const string CDRDAO_TRACK_TYPE_MODE1_RAW = "MODE1_RAW"; + /// Mode 2 mixed formless, cooked, 2336 bytes/sector + const string CDRDAO_TRACK_TYPE_MODE2 = "MODE2"; + /// Mode 2 form 1 track, cooked, 2048 bytes/sector + const string CDRDAO_TRACK_TYPE_MODE2_FORM1 = "MODE2_FORM1"; + /// Mode 2 form 2 track, cooked, 2324 bytes/sector + const string CDRDAO_TRACK_TYPE_MODE2_FORM2 = "MODE2_FORM2"; + /// Mode 2 mixed forms track, cooked, 2336 bytes/sector + const string CDRDAO_TRACK_TYPE_MODE2_MIX = "MODE2_FORM_MIX"; + /// Mode 2 track, raw, 2352 bytes/sector + const string CDRDAO_TRACK_TYPE_MODE2_RAW = "MODE2_RAW"; + + const string REGEX_COMMENT = @"^\s*\/{2}(?.+)$"; + const string REGEX_COPY = @"^\s*(?NO)?\s*COPY"; + const string REGEX_DISCTYPE = @"^\s*(?(CD_DA|CD_ROM_XA|CD_ROM|CD_I))"; + const string REGEX_EMPHASIS = @"^\s*(?NO)?\s*PRE_EMPHASIS"; + const string REGEX_FILE_AUDIO = + @"^\s*(AUDIO)?FILE\s*""(?.+)""\s*(#(?\d+))?\s*((?[\d]+:[\d]+:[\d]+)|(?\d+))\s*(?[\d]+:[\d]+:[\d]+)?"; + const string REGEX_FILE_DATA = + @"^\s*DATAFILE\s*""(?.+)""\s*(#(?\d+))?\s*(?[\d]+:[\d]+:[\d]+)?"; + const string REGEX_INDEX = @"^\s*INDEX\s*(?
\d+:\d+:\d+)"; + const string REGEX_ISRC = @"^\s*ISRC\s*""(?[A-Z0-9]{5,5}[0-9]{7,7})"""; + const string REGEX_MCN = @"^\s*CATALOG\s*""(?[\x21-\x7F]{13,13})"""; + const string REGEX_PREGAP = @"^\s*START\s*(?
\d+:\d+:\d+)?"; + const string REGEX_STEREO = @"^\s*(?(TWO|FOUR))_CHANNEL_AUDIO"; + const string REGEX_TRACK = + @"^\s*TRACK\s*(?(AUDIO|MODE1_RAW|MODE1|MODE2_FORM1|MODE2_FORM2|MODE2_FORM_MIX|MODE2_RAW|MODE2))\s*(?(RW_RAW|RW))?"; + const string REGEX_ZERO_AUDIO = @"^\s*SILENCE\s*(?\d+:\d+:\d+)"; + const string REGEX_ZERO_DATA = @"^\s*ZERO\s*(?\d+:\d+:\d+)"; + const string REGEX_ZERO_PREGAP = @"^\s*PREGAP\s*(?\d+:\d+:\d+)"; + + // CD-Text + const string REGEX_ARRANGER = @"^\s*ARRANGER\s*""(?.+)"""; + const string REGEX_COMPOSER = @"^\s*COMPOSER\s*""(?.+)"""; + const string REGEX_DISC_ID = @"^\s*DISC_ID\s*""(?.+)"""; + const string REGEX_MESSAGE = @"^\s*MESSAGE\s*""(?.+)"""; + const string REGEX_PERFORMER = @"^\s*PERFORMER\s*""(?.+)"""; + const string REGEX_SONGWRITER = @"^\s*SONGWRITER\s*""(?.+)"""; + const string REGEX_TITLE = @"^\s*TITLE\s*""(?.+)"""; + const string REGEX_UPC = @"^\s*UPC_EAN\s*""(?<catalog>[\d]{13,13})"""; + + // Unused + const string REGEX_CD_TEXT = @"^\s*CD_TEXT\s*\{"; + const string REGEX_CLOSURE = @"^\s*\}"; + const string REGEX_LANGUAGE = @"^\s*LANGUAGE\s*(?<code>\d+)\s*\{"; + const string REGEX_LANGUAGE_MAP = @"^\s*LANGUAGE_MAP\s*\{"; + const string REGEX_LANGUAGE_MAPPING = @"^\s*(?<code>\d+)\s?\:\s?(?<language>\d+|\w+)"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/Helpers.cs b/DiscImageChef.DiscImages/CDRDAO/Helpers.cs new file mode 100644 index 000000000..cdeb9ab81 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/Helpers.cs @@ -0,0 +1,114 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Cdrdao + { + static ushort CdrdaoTrackTypeToBytesPerSector(string trackType) + { + switch(trackType) + { + case CDRDAO_TRACK_TYPE_MODE1: + case CDRDAO_TRACK_TYPE_MODE2_FORM1: return 2048; + case CDRDAO_TRACK_TYPE_MODE2_FORM2: return 2324; + case CDRDAO_TRACK_TYPE_MODE2: + case CDRDAO_TRACK_TYPE_MODE2_MIX: return 2336; + case CDRDAO_TRACK_TYPE_AUDIO: + case CDRDAO_TRACK_TYPE_MODE1_RAW: + case CDRDAO_TRACK_TYPE_MODE2_RAW: return 2352; + default: return 0; + } + } + + static ushort CdrdaoTrackTypeToCookedBytesPerSector(string trackType) + { + switch(trackType) + { + case CDRDAO_TRACK_TYPE_MODE1: + case CDRDAO_TRACK_TYPE_MODE2_FORM1: + case CDRDAO_TRACK_TYPE_MODE1_RAW: return 2048; + case CDRDAO_TRACK_TYPE_MODE2_FORM2: return 2324; + case CDRDAO_TRACK_TYPE_MODE2: + case CDRDAO_TRACK_TYPE_MODE2_MIX: + case CDRDAO_TRACK_TYPE_MODE2_RAW: return 2336; + case CDRDAO_TRACK_TYPE_AUDIO: return 2352; + default: return 0; + } + } + + static TrackType CdrdaoTrackTypeToTrackType(string trackType) + { + switch(trackType) + { + case CDRDAO_TRACK_TYPE_MODE1: + case CDRDAO_TRACK_TYPE_MODE1_RAW: return TrackType.CdMode1; + case CDRDAO_TRACK_TYPE_MODE2_FORM1: return TrackType.CdMode2Form1; + case CDRDAO_TRACK_TYPE_MODE2_FORM2: return TrackType.CdMode2Form2; + case CDRDAO_TRACK_TYPE_MODE2: + case CDRDAO_TRACK_TYPE_MODE2_MIX: + case CDRDAO_TRACK_TYPE_MODE2_RAW: return TrackType.CdMode2Formless; + case CDRDAO_TRACK_TYPE_AUDIO: return TrackType.Audio; + default: return TrackType.Data; + } + } + + static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) + { + return ((byte)(sector / 75 / 60), (byte)(sector / 75 % 60), (byte)(sector % 75)); + } + + static string GetTrackMode(Track track) + { + switch(track.TrackType) + { + case TrackType.Audio when track.TrackRawBytesPerSector == 2352: return CDRDAO_TRACK_TYPE_AUDIO; + case TrackType.Data: return CDRDAO_TRACK_TYPE_MODE1; + case TrackType.CdMode1 when track.TrackRawBytesPerSector == 2352: return CDRDAO_TRACK_TYPE_MODE1_RAW; + case TrackType.CdMode2Formless + when track.TrackRawBytesPerSector != 2352: return CDRDAO_TRACK_TYPE_MODE2; + case TrackType.CdMode2Form1 + when track.TrackRawBytesPerSector != 2352: return CDRDAO_TRACK_TYPE_MODE2_FORM1; + case TrackType.CdMode2Form2 + when track.TrackRawBytesPerSector != 2352: return CDRDAO_TRACK_TYPE_MODE2_FORM2; + case TrackType.CdMode2Formless when track.TrackRawBytesPerSector == 2352: + case TrackType.CdMode2Form1 when track.TrackRawBytesPerSector == 2352: + case TrackType.CdMode2Form2 + when track.TrackRawBytesPerSector == 2352: return CDRDAO_TRACK_TYPE_MODE2_RAW; + default: return CDRDAO_TRACK_TYPE_MODE1; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/Identify.cs b/DiscImageChef.DiscImages/CDRDAO/Identify.cs new file mode 100644 index 000000000..efbef2308 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/Identify.cs @@ -0,0 +1,98 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Cdrdao + { + public bool Identify(IFilter imageFilter) + { + try + { + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + byte[] testArray = new byte[512]; + imageFilter.GetDataForkStream().Read(testArray, 0, 512); + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin + bool twoConsecutiveNulls = false; + for(int i = 0; i < 512; i++) + { + if(i >= imageFilter.GetDataForkStream().Length) break; + + if(testArray[i] == 0) + { + if(twoConsecutiveNulls) return false; + + twoConsecutiveNulls = true; + } + else twoConsecutiveNulls = false; + + if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) + return false; + } + + tocStream = new StreamReader(imageFilter.GetDataForkStream()); + + Regex cr = new Regex(REGEX_COMMENT); + Regex dr = new Regex(REGEX_DISCTYPE); + + while(tocStream.Peek() >= 0) + { + string line = tocStream.ReadLine(); + + Match dm = dr.Match(line ?? throw new InvalidOperationException()); + Match cm = cr.Match(line); + + // Skip comments at start of file + if(cm.Success) continue; + + return dm.Success; + } + + return false; + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", cdrdaoFilter.GetFilename()); + DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); + DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); + return false; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/Properties.cs b/DiscImageChef.DiscImages/CDRDAO/Properties.cs new file mode 100644 index 000000000..317e669c5 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/Properties.cs @@ -0,0 +1,124 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Cdrdao + { + public ImageInfo Info => imageInfo; + public string Name => "CDRDAO tocfile"; + public Guid Id => new Guid("04D7BA12-1BE8-44D4-97A4-1B48A505463E"); + public string Format => "CDRDAO tocfile"; + public List<Partition> Partitions { get; private set; } + public List<Session> Sessions => throw new NotImplementedException(); + + public List<Track> Tracks + { + get + { + List<Track> tracks = new List<Track>(); + + foreach(CdrdaoTrack cdrTrack in discimage.Tracks) + { + Track dicTrack = new Track + { + Indexes = cdrTrack.Indexes, + TrackDescription = cdrTrack.Title, + TrackStartSector = cdrTrack.StartSector, + TrackPregap = cdrTrack.Pregap, + TrackSession = 1, + TrackSequence = cdrTrack.Sequence, + TrackType = CdrdaoTrackTypeToTrackType(cdrTrack.Tracktype), + TrackFilter = cdrTrack.Trackfile.Datafilter, + TrackFile = cdrTrack.Trackfile.Datafilter.GetFilename(), + TrackFileOffset = cdrTrack.Trackfile.Offset, + TrackFileType = cdrTrack.Trackfile.Filetype, + TrackRawBytesPerSector = cdrTrack.Bps, + TrackBytesPerSector = CdrdaoTrackTypeToCookedBytesPerSector(cdrTrack.Tracktype) + }; + + dicTrack.TrackEndSector = dicTrack.TrackStartSector + cdrTrack.Sectors - 1; + if(!cdrTrack.Indexes.TryGetValue(0, out dicTrack.TrackStartSector)) + cdrTrack.Indexes.TryGetValue(1, out dicTrack.TrackStartSector); + if(cdrTrack.Subchannel) + { + dicTrack.TrackSubchannelType = cdrTrack.Packedsubchannel + ? TrackSubchannelType.PackedInterleaved + : TrackSubchannelType.RawInterleaved; + dicTrack.TrackSubchannelFilter = cdrTrack.Trackfile.Datafilter; + dicTrack.TrackSubchannelFile = cdrTrack.Trackfile.Datafilter.GetFilename(); + dicTrack.TrackSubchannelOffset = cdrTrack.Trackfile.Offset; + } + else dicTrack.TrackSubchannelType = TrackSubchannelType.None; + + tracks.Add(dicTrack); + } + + return tracks; + } + } + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + // TODO: Decode CD-Text to text + public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.CD_MCN}; + public IEnumerable<SectorTagType> SupportedSectorTags => + new[] + { + SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, + SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubchannel, + SectorTagType.CdSectorSubHeader, SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, + SectorTagType.CdTrackIsrc + }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, + MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, + MediaType.CDV, MediaType.DDCD, MediaType.DDCDR, MediaType.DDCDRW, MediaType.JaguarCD, MediaType.MEGACD, + MediaType.PD650, MediaType.PD650_WORM, MediaType.PS1CD, MediaType.PS2CD, MediaType.SuperCDROM2, + MediaType.SVCD, MediaType.SATURNCD, MediaType.ThreeDO, MediaType.VCD, MediaType.VCDHD, + MediaType.NeoGeoCD, MediaType.PCFX + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new[] {("separate", typeof(bool), "Write each track to a separate file.")}; + public IEnumerable<string> KnownExtensions => new[] {".toc"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO.cs b/DiscImageChef.DiscImages/CDRDAO/Read.cs similarity index 59% rename from DiscImageChef.DiscImages/CDRDAO.cs rename to DiscImageChef.DiscImages/CDRDAO/Read.cs index 7ac6cb3ca..83ec47a54 100644 --- a/DiscImageChef.DiscImages/CDRDAO.cs +++ b/DiscImageChef.DiscImages/CDRDAO/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : CDRDAO.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages cdrdao cuesheets (toc/bin). +// Reads cdrdao cuesheets (toc/bin). // // --[ License ] -------------------------------------------------------------- // @@ -32,7 +32,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -44,220 +43,11 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // TODO: Doesn't support compositing from several files - // TODO: Doesn't support silences that are not in files - public class Cdrdao : IWritableImage + public partial class Cdrdao { - /// <summary>Audio track, 2352 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_AUDIO = "AUDIO"; - /// <summary>Mode 1 track, cooked, 2048 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_MODE1 = "MODE1"; - /// <summary>Mode 1 track, raw, 2352 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_MODE1_RAW = "MODE1_RAW"; - /// <summary>Mode 2 mixed formless, cooked, 2336 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_MODE2 = "MODE2"; - /// <summary>Mode 2 form 1 track, cooked, 2048 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_MODE2_FORM1 = "MODE2_FORM1"; - /// <summary>Mode 2 form 2 track, cooked, 2324 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_MODE2_FORM2 = "MODE2_FORM2"; - /// <summary>Mode 2 mixed forms track, cooked, 2336 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_MODE2_MIX = "MODE2_FORM_MIX"; - /// <summary>Mode 2 track, raw, 2352 bytes/sector</summary> - const string CDRDAO_TRACK_TYPE_MODE2_RAW = "MODE2_RAW"; - - const string REGEX_COMMENT = @"^\s*\/{2}(?<comment>.+)$"; - const string REGEX_COPY = @"^\s*(?<no>NO)?\s*COPY"; - const string REGEX_DISCTYPE = @"^\s*(?<type>(CD_DA|CD_ROM_XA|CD_ROM|CD_I))"; - const string REGEX_EMPHASIS = @"^\s*(?<no>NO)?\s*PRE_EMPHASIS"; - const string REGEX_FILE_AUDIO = - @"^\s*(AUDIO)?FILE\s*""(?<filename>.+)""\s*(#(?<base_offset>\d+))?\s*((?<start>[\d]+:[\d]+:[\d]+)|(?<start_num>\d+))\s*(?<length>[\d]+:[\d]+:[\d]+)?"; - const string REGEX_FILE_DATA = - @"^\s*DATAFILE\s*""(?<filename>.+)""\s*(#(?<base_offset>\d+))?\s*(?<length>[\d]+:[\d]+:[\d]+)?"; - const string REGEX_INDEX = @"^\s*INDEX\s*(?<address>\d+:\d+:\d+)"; - const string REGEX_ISRC = @"^\s*ISRC\s*""(?<isrc>[A-Z0-9]{5,5}[0-9]{7,7})"""; - const string REGEX_MCN = @"^\s*CATALOG\s*""(?<catalog>[\x21-\x7F]{13,13})"""; - const string REGEX_PREGAP = @"^\s*START\s*(?<address>\d+:\d+:\d+)?"; - const string REGEX_STEREO = @"^\s*(?<num>(TWO|FOUR))_CHANNEL_AUDIO"; - const string REGEX_TRACK = - @"^\s*TRACK\s*(?<type>(AUDIO|MODE1_RAW|MODE1|MODE2_FORM1|MODE2_FORM2|MODE2_FORM_MIX|MODE2_RAW|MODE2))\s*(?<subchan>(RW_RAW|RW))?"; - const string REGEX_ZERO_AUDIO = @"^\s*SILENCE\s*(?<length>\d+:\d+:\d+)"; - const string REGEX_ZERO_DATA = @"^\s*ZERO\s*(?<length>\d+:\d+:\d+)"; - const string REGEX_ZERO_PREGAP = @"^\s*PREGAP\s*(?<length>\d+:\d+:\d+)"; - - // CD-Text - const string REGEX_ARRANGER = @"^\s*ARRANGER\s*""(?<arranger>.+)"""; - const string REGEX_COMPOSER = @"^\s*COMPOSER\s*""(?<composer>.+)"""; - const string REGEX_DISC_ID = @"^\s*DISC_ID\s*""(?<discid>.+)"""; - const string REGEX_MESSAGE = @"^\s*MESSAGE\s*""(?<message>.+)"""; - const string REGEX_PERFORMER = @"^\s*PERFORMER\s*""(?<performer>.+)"""; - const string REGEX_SONGWRITER = @"^\s*SONGWRITER\s*""(?<songwriter>.+)"""; - const string REGEX_TITLE = @"^\s*TITLE\s*""(?<title>.+)"""; - const string REGEX_UPC = @"^\s*UPC_EAN\s*""(?<catalog>[\d]{13,13})"""; - - // Unused - const string REGEX_CD_TEXT = @"^\s*CD_TEXT\s*\{"; - const string REGEX_CLOSURE = @"^\s*\}"; - const string REGEX_LANGUAGE = @"^\s*LANGUAGE\s*(?<code>\d+)\s*\{"; - const string REGEX_LANGUAGE_MAP = @"^\s*LANGUAGE_MAP\s*\{"; - const string REGEX_LANGUAGE_MAPPING = @"^\s*(?<code>\d+)\s?\:\s?(?<language>\d+|\w+)"; - - IFilter cdrdaoFilter; - StreamWriter descriptorStream; - CdrdaoDisc discimage; - ImageInfo imageInfo; - Stream imageStream; - /// <summary>Dictionary, index is track #, value is TrackFile</summary> - Dictionary<uint, ulong> offsetmap; - bool separateTracksWriting; - StreamReader tocStream; - Dictionary<byte, byte> trackFlags; - Dictionary<byte, string> trackIsrcs; - string writingBaseName; - Dictionary<uint, FileStream> writingStreams; - List<Track> writingTracks; - - public Cdrdao() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = true, - HasSessions = true, - Version = null, - ApplicationVersion = null, - MediaTitle = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "CDRDAO tocfile"; - public Guid Id => new Guid("04D7BA12-1BE8-44D4-97A4-1B48A505463E"); - - public string Format => "CDRDAO tocfile"; - - public List<Partition> Partitions { get; private set; } - - public List<Track> Tracks - { - get - { - List<Track> tracks = new List<Track>(); - - foreach(CdrdaoTrack cdrTrack in discimage.Tracks) - { - Track dicTrack = new Track - { - Indexes = cdrTrack.Indexes, - TrackDescription = cdrTrack.Title, - TrackStartSector = cdrTrack.StartSector, - TrackPregap = cdrTrack.Pregap, - TrackSession = 1, - TrackSequence = cdrTrack.Sequence, - TrackType = CdrdaoTrackTypeToTrackType(cdrTrack.Tracktype), - TrackFilter = cdrTrack.Trackfile.Datafilter, - TrackFile = cdrTrack.Trackfile.Datafilter.GetFilename(), - TrackFileOffset = cdrTrack.Trackfile.Offset, - TrackFileType = cdrTrack.Trackfile.Filetype, - TrackRawBytesPerSector = cdrTrack.Bps, - TrackBytesPerSector = CdrdaoTrackTypeToCookedBytesPerSector(cdrTrack.Tracktype) - }; - - dicTrack.TrackEndSector = dicTrack.TrackStartSector + cdrTrack.Sectors - 1; - if(!cdrTrack.Indexes.TryGetValue(0, out dicTrack.TrackStartSector)) - cdrTrack.Indexes.TryGetValue(1, out dicTrack.TrackStartSector); - if(cdrTrack.Subchannel) - { - dicTrack.TrackSubchannelType = cdrTrack.Packedsubchannel - ? TrackSubchannelType.PackedInterleaved - : TrackSubchannelType.RawInterleaved; - dicTrack.TrackSubchannelFilter = cdrTrack.Trackfile.Datafilter; - dicTrack.TrackSubchannelFile = cdrTrack.Trackfile.Datafilter.GetFilename(); - dicTrack.TrackSubchannelOffset = cdrTrack.Trackfile.Offset; - } - else dicTrack.TrackSubchannelType = TrackSubchannelType.None; - - tracks.Add(dicTrack); - } - - return tracks; - } - } - - public List<Session> Sessions => throw new NotImplementedException(); - - public bool Identify(IFilter imageFilter) - { - try - { - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - byte[] testArray = new byte[512]; - imageFilter.GetDataForkStream().Read(testArray, 0, 512); - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin - bool twoConsecutiveNulls = false; - for(int i = 0; i < 512; i++) - { - if(i >= imageFilter.GetDataForkStream().Length) break; - - if(testArray[i] == 0) - { - if(twoConsecutiveNulls) return false; - - twoConsecutiveNulls = true; - } - else twoConsecutiveNulls = false; - - if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) - return false; - } - - tocStream = new StreamReader(imageFilter.GetDataForkStream()); - - Regex cr = new Regex(REGEX_COMMENT); - Regex dr = new Regex(REGEX_DISCTYPE); - - while(tocStream.Peek() >= 0) - { - string line = tocStream.ReadLine(); - - Match dm = dr.Match(line ?? throw new InvalidOperationException()); - Match cm = cr.Match(line); - - // Skip comments at start of file - if(cm.Success) continue; - - return dm.Success; - } - - return false; - } - catch(Exception ex) - { - DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", cdrdaoFilter.GetFilename()); - DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); - DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); - return false; - } - } - public bool Open(IFilter imageFilter) { if(imageFilter == null) return false; @@ -1488,860 +1278,5 @@ namespace DiscImageChef.DiscImages return failingLbas.Count <= 0; } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - // TODO: Decode CD-Text to text - public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.CD_MCN}; - public IEnumerable<SectorTagType> SupportedSectorTags => - new[] - { - SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, - SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubchannel, - SectorTagType.CdSectorSubHeader, SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, - SectorTagType.CdTrackIsrc - }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, - MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, - MediaType.CDV, MediaType.DDCD, MediaType.DDCDR, MediaType.DDCDRW, MediaType.JaguarCD, MediaType.MEGACD, - MediaType.PD650, MediaType.PD650_WORM, MediaType.PS1CD, MediaType.PS2CD, MediaType.SuperCDROM2, - MediaType.SVCD, MediaType.SATURNCD, MediaType.ThreeDO, MediaType.VCD, MediaType.VCDHD, - MediaType.NeoGeoCD, MediaType.PCFX - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new[] {("separate", typeof(bool), "Write each track to a separate file.")}; - public IEnumerable<string> KnownExtensions => new[] {".toc"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(options != null) - { - if(options.TryGetValue("separate", out string tmpValue)) - { - if(!bool.TryParse(tmpValue, out separateTracksWriting)) - { - ErrorMessage = "Invalid value for split option"; - return false; - } - - if(separateTracksWriting) - { - ErrorMessage = "Separate tracksnot yet implemented"; - return false; - } - } - } - else separateTracksWriting = false; - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - // TODO: Separate tracks - try - { - writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); - descriptorStream = new StreamWriter(path, false, Encoding.ASCII); - } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - discimage = new CdrdaoDisc {Disktype = mediaType, Tracks = new List<CdrdaoTrack>()}; - - trackFlags = new Dictionary<byte, byte>(); - trackIsrcs = new Dictionary<byte, string>(); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - switch(tag) - { - case MediaTagType.CD_MCN: - discimage.Mcn = Encoding.ASCII.GetString(data); - return true; - default: - ErrorMessage = $"Unsupported media tag {tag}"; - return false; - } - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) - { - ErrorMessage = "Invalid write mode for this sector"; - return false; - } - - if(data.Length != track.TrackRawBytesPerSector) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - // cdrdao audio tracks are endian swapped corresponding to DiscImageChef - if(track.TrackType == TrackType.Audio) - { - byte[] swapped = new byte[data.Length]; - for(long i = 0; i < swapped.Length; i += 2) - { - swapped[i] = data[i + 1]; - swapped[i + 1] = data[i]; - } - - data = swapped; - } - - trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) - { - ErrorMessage = "Invalid write mode for this sector"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - if(data.Length % track.TrackRawBytesPerSector != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - // cdrdao audio tracks are endian swapped corresponding to DiscImageChef - if(track.TrackType == TrackType.Audio) - { - byte[] swapped = new byte[data.Length]; - for(long i = 0; i < swapped.Length; i += 2) - { - swapped[i] = data[i + 1]; - swapped[i + 1] = data[i]; - } - - data = swapped; - } - - switch(track.TrackSubchannelType) - { - case TrackSubchannelType.None: - trackStream - .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - case TrackSubchannelType.Raw: - case TrackSubchannelType.RawInterleaved: - trackStream - .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)), - SeekOrigin.Begin); - for(uint i = 0; i < length; i++) - { - trackStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); - trackStream.Position += 96; - } - - ErrorMessage = ""; - return true; - default: - ErrorMessage = "Invalid subchannel mode for this sector"; - return false; - } - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(data.Length != track.TrackRawBytesPerSector) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - // cdrdao audio tracks are endian swapped corresponding to DiscImageChef - if(track.TrackType == TrackType.Audio) - { - byte[] swapped = new byte[data.Length]; - for(long i = 0; i < swapped.Length; i += 2) - { - swapped[i] = data[i + 1]; - swapped[i + 1] = data[i]; - } - - data = swapped; - } - - uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); - - trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - return true; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - if(data.Length % track.TrackRawBytesPerSector != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - // cdrdao audio tracks are endian swapped corresponding to DiscImageChef - if(track.TrackType == TrackType.Audio) - { - byte[] swapped = new byte[data.Length]; - for(long i = 0; i < swapped.Length; i += 2) - { - swapped[i] = data[i + 1]; - swapped[i + 1] = data[i]; - } - - data = swapped; - } - - uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); - - for(uint i = 0; i < length; i++) - { - trackStream.Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), - SeekOrigin.Begin); - trackStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); - } - - return true; - } - - public bool SetTracks(List<Track> tracks) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(tracks == null || tracks.Count == 0) - { - ErrorMessage = "Invalid tracks sent"; - return false; - } - - if(writingTracks != null && writingStreams != null) - foreach(FileStream oldTrack in writingStreams.Select(t => t.Value).Distinct()) - oldTrack.Close(); - - ulong currentOffset = 0; - writingTracks = new List<Track>(); - foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) - { - if(track.TrackSubchannelType == TrackSubchannelType.Q16 || - track.TrackSubchannelType == TrackSubchannelType.Q16Interleaved) - { - ErrorMessage = - $"Unsupported subchannel type {track.TrackSubchannelType} for track {track.TrackSequence}"; - return false; - } - - Track newTrack = track; - newTrack.TrackFile = separateTracksWriting - ? writingBaseName + $"_track{track.TrackSequence:D2}.bin" - : writingBaseName + ".bin"; - newTrack.TrackFileOffset = separateTracksWriting ? 0 : currentOffset; - writingTracks.Add(newTrack); - currentOffset += (ulong)newTrack.TrackRawBytesPerSector * - (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); - - if(track.TrackSubchannelType != TrackSubchannelType.None) - currentOffset += 96 * (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); - } - - writingStreams = new Dictionary<uint, FileStream>(); - if(separateTracksWriting) - foreach(Track track in writingTracks) - writingStreams.Add(track.TrackSequence, - new FileStream(track.TrackFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, - FileShare.None)); - else - { - FileStream jointstream = new FileStream(writingBaseName + ".bin", FileMode.OpenOrCreate, - FileAccess.ReadWrite, FileShare.None); - foreach(Track track in writingTracks) writingStreams.Add(track.TrackSequence, jointstream); - } - - return true; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(separateTracksWriting) - foreach(FileStream writingStream in writingStreams.Values) - { - writingStream.Flush(); - writingStream.Close(); - } - else - { - writingStreams.First().Value.Flush(); - writingStreams.First().Value.Close(); - } - - bool data = writingTracks.Count(t => t.TrackType != TrackType.Audio) > 0; - bool mode2 = writingTracks.Count(t => t.TrackType == TrackType.CdMode2Form1 || - t.TrackType == TrackType.CdMode2Form2 || - t.TrackType == TrackType.CdMode2Formless) > 0; - - if(mode2) descriptorStream.WriteLine("CD_ROM_XA"); - else if(data) descriptorStream.WriteLine("CD_ROM"); - else descriptorStream.WriteLine("CD_DA"); - - if(!string.IsNullOrWhiteSpace(discimage.Comment)) - { - string[] commentLines = discimage.Comment.Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries); - foreach(string line in commentLines) descriptorStream.WriteLine("// {0}", line); - } - - descriptorStream.WriteLine(); - - if(!string.IsNullOrEmpty(discimage.Mcn)) descriptorStream.WriteLine("CATALOG {0}", discimage.Mcn); - - foreach(Track track in writingTracks) - { - descriptorStream.WriteLine(); - descriptorStream.WriteLine("// Track {0}", track.TrackSequence); - - string subchannelType; - - switch(track.TrackSubchannelType) - { - case TrackSubchannelType.Packed: - case TrackSubchannelType.PackedInterleaved: - subchannelType = " RW"; - break; - case TrackSubchannelType.Raw: - case TrackSubchannelType.RawInterleaved: - subchannelType = " RW_RAW"; - break; - default: - subchannelType = ""; - break; - } - - descriptorStream.WriteLine("TRACK {0}{1}", GetTrackMode(track), subchannelType); - - trackFlags.TryGetValue((byte)track.TrackSequence, out byte flagsByte); - - CdFlags flags = (CdFlags)flagsByte; - - descriptorStream.WriteLine("{0}COPY", flags.HasFlag(CdFlags.CopyPermitted) ? "" : "NO "); - - if(track.TrackType == TrackType.Audio) - { - descriptorStream.WriteLine("{0}PRE_EMPHASIS", flags.HasFlag(CdFlags.PreEmphasis) ? "" : "NO "); - descriptorStream.WriteLine("{0}_CHANNEL_AUDIO", - flags.HasFlag(CdFlags.FourChannel) ? "FOUR" : "TWO"); - } - - if(trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc)) - descriptorStream.WriteLine("ISRC {0}", isrc); - - (byte minute, byte second, byte frame) - msf = LbaToMsf(track.TrackEndSector - track.TrackStartSector + 1); - - descriptorStream.WriteLine("DATAFILE \"{0}\" #{1} {2:D2}:{3:D2}:{4:D2} // length in bytes: {5}", - Path.GetFileName(track.TrackFile), track.TrackFileOffset, msf.minute, - msf.second, msf.frame, - (track.TrackEndSector - track.TrackStartSector + 1) * - (ulong)(track.TrackRawBytesPerSector + - (track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0))); - - descriptorStream.WriteLine(); - } - - descriptorStream.Flush(); - descriptorStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - discimage.Barcode = metadata.MediaBarcode; - discimage.Comment = metadata.Comments; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - { - if(data.Length != 1) - { - ErrorMessage = "Incorrect data size for track flags"; - return false; - } - - trackFlags.Add((byte)track.TrackSequence, data[0]); - - return true; - } - case SectorTagType.CdTrackIsrc: - { - if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); - return true; - } - case SectorTagType.CdSectorSubchannel: - { - if(track.TrackSubchannelType == 0) - { - ErrorMessage = - $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; - return false; - } - - if(data.Length != 96) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - trackStream - .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - return true; - } - default: - ErrorMessage = $"Unsupported tag type {tag}"; - return false; - } - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); - case SectorTagType.CdSectorSubchannel: - { - if(track.TrackSubchannelType == 0) - { - ErrorMessage = - $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; - return false; - } - - if(data.Length % 96 != 0) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - for(uint i = 0; i < length; i++) - { - trackStream - .Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, - SeekOrigin.Begin); - trackStream.Write(data, (int)(i * 96), 96); - } - - return true; - } - default: - ErrorMessage = $"Unsupported tag type {tag}"; - return false; - } - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - static ushort CdrdaoTrackTypeToBytesPerSector(string trackType) - { - switch(trackType) - { - case CDRDAO_TRACK_TYPE_MODE1: - case CDRDAO_TRACK_TYPE_MODE2_FORM1: return 2048; - case CDRDAO_TRACK_TYPE_MODE2_FORM2: return 2324; - case CDRDAO_TRACK_TYPE_MODE2: - case CDRDAO_TRACK_TYPE_MODE2_MIX: return 2336; - case CDRDAO_TRACK_TYPE_AUDIO: - case CDRDAO_TRACK_TYPE_MODE1_RAW: - case CDRDAO_TRACK_TYPE_MODE2_RAW: return 2352; - default: return 0; - } - } - - static ushort CdrdaoTrackTypeToCookedBytesPerSector(string trackType) - { - switch(trackType) - { - case CDRDAO_TRACK_TYPE_MODE1: - case CDRDAO_TRACK_TYPE_MODE2_FORM1: - case CDRDAO_TRACK_TYPE_MODE1_RAW: return 2048; - case CDRDAO_TRACK_TYPE_MODE2_FORM2: return 2324; - case CDRDAO_TRACK_TYPE_MODE2: - case CDRDAO_TRACK_TYPE_MODE2_MIX: - case CDRDAO_TRACK_TYPE_MODE2_RAW: return 2336; - case CDRDAO_TRACK_TYPE_AUDIO: return 2352; - default: return 0; - } - } - - static TrackType CdrdaoTrackTypeToTrackType(string trackType) - { - switch(trackType) - { - case CDRDAO_TRACK_TYPE_MODE1: - case CDRDAO_TRACK_TYPE_MODE1_RAW: return TrackType.CdMode1; - case CDRDAO_TRACK_TYPE_MODE2_FORM1: return TrackType.CdMode2Form1; - case CDRDAO_TRACK_TYPE_MODE2_FORM2: return TrackType.CdMode2Form2; - case CDRDAO_TRACK_TYPE_MODE2: - case CDRDAO_TRACK_TYPE_MODE2_MIX: - case CDRDAO_TRACK_TYPE_MODE2_RAW: return TrackType.CdMode2Formless; - case CDRDAO_TRACK_TYPE_AUDIO: return TrackType.Audio; - default: return TrackType.Data; - } - } - - static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) - { - return ((byte)(sector / 75 / 60), (byte)(sector / 75 % 60), (byte)(sector % 75)); - } - - static string GetTrackMode(Track track) - { - switch(track.TrackType) - { - case TrackType.Audio when track.TrackRawBytesPerSector == 2352: return CDRDAO_TRACK_TYPE_AUDIO; - case TrackType.Data: return CDRDAO_TRACK_TYPE_MODE1; - case TrackType.CdMode1 when track.TrackRawBytesPerSector == 2352: return CDRDAO_TRACK_TYPE_MODE1_RAW; - case TrackType.CdMode2Formless - when track.TrackRawBytesPerSector != 2352: return CDRDAO_TRACK_TYPE_MODE2; - case TrackType.CdMode2Form1 - when track.TrackRawBytesPerSector != 2352: return CDRDAO_TRACK_TYPE_MODE2_FORM1; - case TrackType.CdMode2Form2 - when track.TrackRawBytesPerSector != 2352: return CDRDAO_TRACK_TYPE_MODE2_FORM2; - case TrackType.CdMode2Formless when track.TrackRawBytesPerSector == 2352: - case TrackType.CdMode2Form1 when track.TrackRawBytesPerSector == 2352: - case TrackType.CdMode2Form2 - when track.TrackRawBytesPerSector == 2352: return CDRDAO_TRACK_TYPE_MODE2_RAW; - default: return CDRDAO_TRACK_TYPE_MODE1; - } - } - - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - struct CdrdaoTrackFile - { - /// <summary>Track #</summary> - public uint Sequence; - /// <summary>Filter of file containing track</summary> - public IFilter Datafilter; - /// <summary>Path of file containing track</summary> - public string Datafile; - /// <summary>Offset of track start in file</summary> - public ulong Offset; - /// <summary>Type of file</summary> - public string Filetype; - } - - #pragma warning disable 169 - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - struct CdrdaoTrack - { - /// <summary>Track #</summary> - public uint Sequence; - /// <summary>Track title (from CD-Text)</summary> - public string Title; - /// <summary>Track genre (from CD-Text)</summary> - public string Genre; - /// <summary>Track arranger (from CD-Text)</summary> - public string Arranger; - /// <summary>Track composer (from CD-Text)</summary> - public string Composer; - /// <summary>Track performer (from CD-Text)</summary> - public string Performer; - /// <summary>Track song writer (from CD-Text)</summary> - public string Songwriter; - /// <summary>Track ISRC</summary> - public string Isrc; - /// <summary>Disk provider's message (from CD-Text)</summary> - public string Message; - /// <summary>File struct for this track</summary> - public CdrdaoTrackFile Trackfile; - /// <summary>Indexes on this track</summary> - public Dictionary<int, ulong> Indexes; - /// <summary>Track pre-gap in sectors</summary> - public ulong Pregap; - /// <summary>Track post-gap in sectors</summary> - public ulong Postgap; - /// <summary>Digical Copy Permitted</summary> - public bool FlagDcp; - /// <summary>Track is quadraphonic</summary> - public bool Flag_4Ch; - /// <summary>Track has preemphasis</summary> - public bool FlagPre; - /// <summary>Bytes per sector</summary> - public ushort Bps; - /// <summary>Sectors in track</summary> - public ulong Sectors; - /// <summary>Starting sector in track</summary> - public ulong StartSector; - /// <summary>Track type</summary> - public string Tracktype; - public bool Subchannel; - public bool Packedsubchannel; - } - - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - struct CdrdaoDisc - { - /// <summary>Disk title (from CD-Text)</summary> - public string Title; - /// <summary>Disk genre (from CD-Text)</summary> - public string Genre; - /// <summary>Disk arranger (from CD-Text)</summary> - public string Arranger; - /// <summary>Disk composer (from CD-Text)</summary> - public string Composer; - /// <summary>Disk performer (from CD-Text)</summary> - public string Performer; - /// <summary>Disk song writer (from CD-Text)</summary> - public string Songwriter; - /// <summary>Disk provider's message (from CD-Text)</summary> - public string Message; - /// <summary>Media catalog number</summary> - public string Mcn; - /// <summary>Disk type</summary> - public MediaType Disktype; - /// <summary>Disk type string</summary> - public string Disktypestr; - /// <summary>Disk CDDB ID</summary> - public string DiskId; - /// <summary>Disk UPC/EAN</summary> - public string Barcode; - /// <summary>Tracks</summary> - public List<CdrdaoTrack> Tracks; - /// <summary>Disk comment</summary> - public string Comment; - } - #pragma warning restore 169 } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/Structs.cs b/DiscImageChef.DiscImages/CDRDAO/Structs.cs new file mode 100644 index 000000000..6cd9b678c --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/Structs.cs @@ -0,0 +1,139 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Cdrdao + { + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + struct CdrdaoTrackFile + { + /// <summary>Track #</summary> + public uint Sequence; + /// <summary>Filter of file containing track</summary> + public IFilter Datafilter; + /// <summary>Path of file containing track</summary> + public string Datafile; + /// <summary>Offset of track start in file</summary> + public ulong Offset; + /// <summary>Type of file</summary> + public string Filetype; + } + + #pragma warning disable 169 + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + struct CdrdaoTrack + { + /// <summary>Track #</summary> + public uint Sequence; + /// <summary>Track title (from CD-Text)</summary> + public string Title; + /// <summary>Track genre (from CD-Text)</summary> + public string Genre; + /// <summary>Track arranger (from CD-Text)</summary> + public string Arranger; + /// <summary>Track composer (from CD-Text)</summary> + public string Composer; + /// <summary>Track performer (from CD-Text)</summary> + public string Performer; + /// <summary>Track song writer (from CD-Text)</summary> + public string Songwriter; + /// <summary>Track ISRC</summary> + public string Isrc; + /// <summary>Disk provider's message (from CD-Text)</summary> + public string Message; + /// <summary>File struct for this track</summary> + public CdrdaoTrackFile Trackfile; + /// <summary>Indexes on this track</summary> + public Dictionary<int, ulong> Indexes; + /// <summary>Track pre-gap in sectors</summary> + public ulong Pregap; + /// <summary>Track post-gap in sectors</summary> + public ulong Postgap; + /// <summary>Digical Copy Permitted</summary> + public bool FlagDcp; + /// <summary>Track is quadraphonic</summary> + public bool Flag_4Ch; + /// <summary>Track has preemphasis</summary> + public bool FlagPre; + /// <summary>Bytes per sector</summary> + public ushort Bps; + /// <summary>Sectors in track</summary> + public ulong Sectors; + /// <summary>Starting sector in track</summary> + public ulong StartSector; + /// <summary>Track type</summary> + public string Tracktype; + public bool Subchannel; + public bool Packedsubchannel; + } + + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + struct CdrdaoDisc + { + /// <summary>Disk title (from CD-Text)</summary> + public string Title; + /// <summary>Disk genre (from CD-Text)</summary> + public string Genre; + /// <summary>Disk arranger (from CD-Text)</summary> + public string Arranger; + /// <summary>Disk composer (from CD-Text)</summary> + public string Composer; + /// <summary>Disk performer (from CD-Text)</summary> + public string Performer; + /// <summary>Disk song writer (from CD-Text)</summary> + public string Songwriter; + /// <summary>Disk provider's message (from CD-Text)</summary> + public string Message; + /// <summary>Media catalog number</summary> + public string Mcn; + /// <summary>Disk type</summary> + public MediaType Disktype; + /// <summary>Disk type string</summary> + public string Disktypestr; + /// <summary>Disk CDDB ID</summary> + public string DiskId; + /// <summary>Disk UPC/EAN</summary> + public string Barcode; + /// <summary>Tracks</summary> + public List<CdrdaoTrack> Tracks; + /// <summary>Disk comment</summary> + public string Comment; + } + #pragma warning restore 169 + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/Unsupported.cs b/DiscImageChef.DiscImages/CDRDAO/Unsupported.cs new file mode 100644 index 000000000..1ba9d879a --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/Unsupported.cs @@ -0,0 +1,42 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Cdrdao + { + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRDAO/Write.cs b/DiscImageChef.DiscImages/CDRDAO/Write.cs new file mode 100644 index 000000000..9acfaf4f9 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRDAO/Write.cs @@ -0,0 +1,698 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes cdrdao cuesheets (toc/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + public partial class Cdrdao + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(options != null) + { + if(options.TryGetValue("separate", out string tmpValue)) + { + if(!bool.TryParse(tmpValue, out separateTracksWriting)) + { + ErrorMessage = "Invalid value for split option"; + return false; + } + + if(separateTracksWriting) + { + ErrorMessage = "Separate tracksnot yet implemented"; + return false; + } + } + } + else separateTracksWriting = false; + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + // TODO: Separate tracks + try + { + writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); + descriptorStream = new StreamWriter(path, false, Encoding.ASCII); + } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + discimage = new CdrdaoDisc {Disktype = mediaType, Tracks = new List<CdrdaoTrack>()}; + + trackFlags = new Dictionary<byte, byte>(); + trackIsrcs = new Dictionary<byte, string>(); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + switch(tag) + { + case MediaTagType.CD_MCN: + discimage.Mcn = Encoding.ASCII.GetString(data); + return true; + default: + ErrorMessage = $"Unsupported media tag {tag}"; + return false; + } + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) + { + ErrorMessage = "Invalid write mode for this sector"; + return false; + } + + if(data.Length != track.TrackRawBytesPerSector) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + // cdrdao audio tracks are endian swapped corresponding to DiscImageChef + if(track.TrackType == TrackType.Audio) + { + byte[] swapped = new byte[data.Length]; + for(long i = 0; i < swapped.Length; i += 2) + { + swapped[i] = data[i + 1]; + swapped[i + 1] = data[i]; + } + + data = swapped; + } + + trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) + { + ErrorMessage = "Invalid write mode for this sector"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + if(data.Length % track.TrackRawBytesPerSector != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + // cdrdao audio tracks are endian swapped corresponding to DiscImageChef + if(track.TrackType == TrackType.Audio) + { + byte[] swapped = new byte[data.Length]; + for(long i = 0; i < swapped.Length; i += 2) + { + swapped[i] = data[i + 1]; + swapped[i + 1] = data[i]; + } + + data = swapped; + } + + switch(track.TrackSubchannelType) + { + case TrackSubchannelType.None: + trackStream + .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + case TrackSubchannelType.Raw: + case TrackSubchannelType.RawInterleaved: + trackStream + .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)), + SeekOrigin.Begin); + for(uint i = 0; i < length; i++) + { + trackStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); + trackStream.Position += 96; + } + + ErrorMessage = ""; + return true; + default: + ErrorMessage = "Invalid subchannel mode for this sector"; + return false; + } + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(data.Length != track.TrackRawBytesPerSector) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + // cdrdao audio tracks are endian swapped corresponding to DiscImageChef + if(track.TrackType == TrackType.Audio) + { + byte[] swapped = new byte[data.Length]; + for(long i = 0; i < swapped.Length; i += 2) + { + swapped[i] = data[i + 1]; + swapped[i + 1] = data[i]; + } + + data = swapped; + } + + uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); + + trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + return true; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + if(data.Length % track.TrackRawBytesPerSector != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + // cdrdao audio tracks are endian swapped corresponding to DiscImageChef + if(track.TrackType == TrackType.Audio) + { + byte[] swapped = new byte[data.Length]; + for(long i = 0; i < swapped.Length; i += 2) + { + swapped[i] = data[i + 1]; + swapped[i + 1] = data[i]; + } + + data = swapped; + } + + uint subchannelSize = (uint)(track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0); + + for(uint i = 0; i < length; i++) + { + trackStream.Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + subchannelSize)), + SeekOrigin.Begin); + trackStream.Write(data, (int)(i * track.TrackRawBytesPerSector), track.TrackRawBytesPerSector); + } + + return true; + } + + public bool SetTracks(List<Track> tracks) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(tracks == null || tracks.Count == 0) + { + ErrorMessage = "Invalid tracks sent"; + return false; + } + + if(writingTracks != null && writingStreams != null) + foreach(FileStream oldTrack in writingStreams.Select(t => t.Value).Distinct()) + oldTrack.Close(); + + ulong currentOffset = 0; + writingTracks = new List<Track>(); + foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) + { + if(track.TrackSubchannelType == TrackSubchannelType.Q16 || + track.TrackSubchannelType == TrackSubchannelType.Q16Interleaved) + { + ErrorMessage = + $"Unsupported subchannel type {track.TrackSubchannelType} for track {track.TrackSequence}"; + return false; + } + + Track newTrack = track; + newTrack.TrackFile = separateTracksWriting + ? writingBaseName + $"_track{track.TrackSequence:D2}.bin" + : writingBaseName + ".bin"; + newTrack.TrackFileOffset = separateTracksWriting ? 0 : currentOffset; + writingTracks.Add(newTrack); + currentOffset += (ulong)newTrack.TrackRawBytesPerSector * + (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); + + if(track.TrackSubchannelType != TrackSubchannelType.None) + currentOffset += 96 * (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); + } + + writingStreams = new Dictionary<uint, FileStream>(); + if(separateTracksWriting) + foreach(Track track in writingTracks) + writingStreams.Add(track.TrackSequence, + new FileStream(track.TrackFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, + FileShare.None)); + else + { + FileStream jointstream = new FileStream(writingBaseName + ".bin", FileMode.OpenOrCreate, + FileAccess.ReadWrite, FileShare.None); + foreach(Track track in writingTracks) writingStreams.Add(track.TrackSequence, jointstream); + } + + return true; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(separateTracksWriting) + foreach(FileStream writingStream in writingStreams.Values) + { + writingStream.Flush(); + writingStream.Close(); + } + else + { + writingStreams.First().Value.Flush(); + writingStreams.First().Value.Close(); + } + + bool data = writingTracks.Count(t => t.TrackType != TrackType.Audio) > 0; + bool mode2 = writingTracks.Count(t => t.TrackType == TrackType.CdMode2Form1 || + t.TrackType == TrackType.CdMode2Form2 || + t.TrackType == TrackType.CdMode2Formless) > 0; + + if(mode2) descriptorStream.WriteLine("CD_ROM_XA"); + else if(data) descriptorStream.WriteLine("CD_ROM"); + else descriptorStream.WriteLine("CD_DA"); + + if(!string.IsNullOrWhiteSpace(discimage.Comment)) + { + string[] commentLines = discimage.Comment.Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries); + foreach(string line in commentLines) descriptorStream.WriteLine("// {0}", line); + } + + descriptorStream.WriteLine(); + + if(!string.IsNullOrEmpty(discimage.Mcn)) descriptorStream.WriteLine("CATALOG {0}", discimage.Mcn); + + foreach(Track track in writingTracks) + { + descriptorStream.WriteLine(); + descriptorStream.WriteLine("// Track {0}", track.TrackSequence); + + string subchannelType; + + switch(track.TrackSubchannelType) + { + case TrackSubchannelType.Packed: + case TrackSubchannelType.PackedInterleaved: + subchannelType = " RW"; + break; + case TrackSubchannelType.Raw: + case TrackSubchannelType.RawInterleaved: + subchannelType = " RW_RAW"; + break; + default: + subchannelType = ""; + break; + } + + descriptorStream.WriteLine("TRACK {0}{1}", GetTrackMode(track), subchannelType); + + trackFlags.TryGetValue((byte)track.TrackSequence, out byte flagsByte); + + CdFlags flags = (CdFlags)flagsByte; + + descriptorStream.WriteLine("{0}COPY", flags.HasFlag(CdFlags.CopyPermitted) ? "" : "NO "); + + if(track.TrackType == TrackType.Audio) + { + descriptorStream.WriteLine("{0}PRE_EMPHASIS", flags.HasFlag(CdFlags.PreEmphasis) ? "" : "NO "); + descriptorStream.WriteLine("{0}_CHANNEL_AUDIO", + flags.HasFlag(CdFlags.FourChannel) ? "FOUR" : "TWO"); + } + + if(trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc)) + descriptorStream.WriteLine("ISRC {0}", isrc); + + (byte minute, byte second, byte frame) + msf = LbaToMsf(track.TrackEndSector - track.TrackStartSector + 1); + + descriptorStream.WriteLine("DATAFILE \"{0}\" #{1} {2:D2}:{3:D2}:{4:D2} // length in bytes: {5}", + Path.GetFileName(track.TrackFile), track.TrackFileOffset, msf.minute, + msf.second, msf.frame, + (track.TrackEndSector - track.TrackStartSector + 1) * + (ulong)(track.TrackRawBytesPerSector + + (track.TrackSubchannelType != TrackSubchannelType.None ? 96 : 0))); + + descriptorStream.WriteLine(); + } + + descriptorStream.Flush(); + descriptorStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + discimage.Barcode = metadata.MediaBarcode; + discimage.Comment = metadata.Comments; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + { + if(data.Length != 1) + { + ErrorMessage = "Incorrect data size for track flags"; + return false; + } + + trackFlags.Add((byte)track.TrackSequence, data[0]); + + return true; + } + case SectorTagType.CdTrackIsrc: + { + if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); + return true; + } + case SectorTagType.CdSectorSubchannel: + { + if(track.TrackSubchannelType == 0) + { + ErrorMessage = + $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; + return false; + } + + if(data.Length != 96) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + trackStream + .Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + return true; + } + default: + ErrorMessage = $"Unsupported tag type {tag}"; + return false; + } + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); + case SectorTagType.CdSectorSubchannel: + { + if(track.TrackSubchannelType == 0) + { + ErrorMessage = + $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; + return false; + } + + if(data.Length % 96 != 0) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + for(uint i = 0; i < length; i++) + { + trackStream + .Seek((long)(track.TrackFileOffset + (i + sectorAddress - track.TrackStartSector) * (ulong)(track.TrackRawBytesPerSector + 96)) + track.TrackRawBytesPerSector, + SeekOrigin.Begin); + trackStream.Write(data, (int)(i * 96), 96); + } + + return true; + } + default: + ErrorMessage = $"Unsupported tag type {tag}"; + return false; + } + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/CDRWin.cs b/DiscImageChef.DiscImages/CDRWin/CDRWin.cs new file mode 100644 index 000000000..45b64f251 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/CDRWin.cs @@ -0,0 +1,83 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CDRWin.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Implement track flags + public partial class CdrWin : IWritableImage + { + IFilter cdrwinFilter; + StreamReader cueStream; + StreamWriter descriptorStream; + CdrWinDisc discimage; + ImageInfo imageInfo; + Stream imageStream; + /// <summary>Dictionary, index is track #, value is TrackFile</summary> + Dictionary<uint, ulong> offsetmap; + bool separateTracksWriting; + Dictionary<byte, byte> trackFlags; + Dictionary<byte, string> trackIsrcs; + string writingBaseName; + Dictionary<uint, FileStream> writingStreams; + List<Track> writingTracks; + + public CdrWin() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = true, + HasSessions = true, + Version = null, + ApplicationVersion = null, + MediaTitle = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/Constants.cs b/DiscImageChef.DiscImages/CDRWin/Constants.cs new file mode 100644 index 000000000..7f408e12d --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/Constants.cs @@ -0,0 +1,161 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class CdrWin + { + // Type for FILE entity + /// <summary>Data as-is in little-endian</summary> + const string CDRWIN_DISK_TYPE_LITTLE_ENDIAN = "BINARY"; + /// <summary>Data as-is in big-endian</summary> + const string CDRWIN_DISK_TYPE_BIG_ENDIAN = "MOTOROLA"; + /// <summary>Audio in Apple AIF file</summary> + const string CDRWIN_DISK_TYPE_AIFF = "AIFF"; + /// <summary>Audio in Microsoft WAV file</summary> + const string CDRWIN_DISK_TYPE_RIFF = "WAVE"; + /// <summary>Audio in MP3 file</summary> + const string CDRWIN_DISK_TYPE_MP3 = "MP3"; + + // Type for TRACK entity + /// <summary>Audio track, 2352 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_AUDIO = "AUDIO"; + /// <summary>CD+G track, 2448 bytes/sector (audio+subchannel)</summary> + const string CDRWIN_TRACK_TYPE_CDG = "CDG"; + /// <summary>Mode 1 track, cooked, 2048 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_MODE1 = "MODE1/2048"; + /// <summary>Mode 1 track, raw, 2352 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_MODE1_RAW = "MODE1/2352"; + /// <summary>Mode 2 form 1 track, cooked, 2048 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_MODE2_FORM1 = "MODE2/2048"; + /// <summary>Mode 2 form 2 track, cooked, 2324 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_MODE2_FORM2 = "MODE2/2324"; + /// <summary>Mode 2 formless track, cooked, 2336 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_MODE2_FORMLESS = "MODE2/2336"; + /// <summary>Mode 2 track, raw, 2352 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_MODE2_RAW = "MODE2/2352"; + /// <summary>CD-i track, cooked, 2336 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_CDI = "CDI/2336"; + /// <summary>CD-i track, raw, 2352 bytes/sector</summary> + const string CDRWIN_TRACK_TYPE_CDI_RAW = "CDI/2352"; + + // Type for REM ORIGINAL MEDIA-TYPE entity + /// <summary>DiskType.CD</summary> + const string CDRWIN_DISK_TYPE_CD = "CD"; + /// <summary>DiskType.CDRW</summary> + const string CDRWIN_DISK_TYPE_CDRW = "CD-RW"; + /// <summary>DiskType.CDMRW</summary> + const string CDRWIN_DISK_TYPE_CDMRW = "CD-MRW"; + /// <summary>DiskType.CDMRW</summary> + const string CDRWIN_DISK_TYPE_CDMRW2 = "CD-(MRW)"; + /// <summary>DiskType.DVDROM</summary> + const string CDRWIN_DISK_TYPE_DVD = "DVD"; + /// <summary>DiskType.DVDPRW</summary> + const string CDRWIN_DISK_TYPE_DVDPMRW = "DVD+MRW"; + /// <summary>DiskType.DVDPRW</summary> + const string CDRWIN_DISK_TYPE_DVDPMRW2 = "DVD+(MRW)"; + /// <summary>DiskType.DVDPRWDL</summary> + const string CDRWIN_DISK_TYPE_DVDPMRWDL = "DVD+MRW DL"; + /// <summary>DiskType.DVDPRWDL</summary> + const string CDRWIN_DISK_TYPE_DVDPMRWDL2 = "DVD+(MRW) DL"; + /// <summary>DiskType.DVDPR</summary> + const string CDRWIN_DISK_TYPE_DVDPR = "DVD+R"; + /// <summary>DiskType.DVDPRDL</summary> + const string CDRWIN_DISK_TYPE_DVDPRDL = "DVD+R DL"; + /// <summary>DiskType.DVDPRW</summary> + const string CDRWIN_DISK_TYPE_DVDPRW = "DVD+RW"; + /// <summary>DiskType.DVDPRWDL</summary> + const string CDRWIN_DISK_TYPE_DVDPRWDL = "DVD+RW DL"; + /// <summary>DiskType.DVDPR</summary> + const string CDRWIN_DISK_TYPE_DVDPVR = "DVD+VR"; + /// <summary>DiskType.DVDRAM</summary> + const string CDRWIN_DISK_TYPE_DVDRAM = "DVD-RAM"; + /// <summary>DiskType.DVDR</summary> + const string CDRWIN_DISK_TYPE_DVDR = "DVD-R"; + /// <summary>DiskType.DVDRDL</summary> + const string CDRWIN_DISK_TYPE_DVDRDL = "DVD-R DL"; + /// <summary>DiskType.DVDRW</summary> + const string CDRWIN_DISK_TYPE_DVDRW = "DVD-RW"; + /// <summary>DiskType.DVDRWDL</summary> + const string CDRWIN_DISK_TYPE_DVDRWDL = "DVD-RW DL"; + /// <summary>DiskType.DVDR</summary> + const string CDRWIN_DISK_TYPE_DVDVR = "DVD-VR"; + /// <summary>DiskType.DVDRW</summary> + const string CDRWIN_DISK_TYPE_DVDRW2 = "DVDRW"; + /// <summary>DiskType.HDDVDROM</summary> + const string CDRWIN_DISK_TYPE_HDDVD = "HD DVD"; + /// <summary>DiskType.HDDVDRAM</summary> + const string CDRWIN_DISK_TYPE_HDDVDRAM = "HD DVD-RAM"; + /// <summary>DiskType.HDDVDR</summary> + const string CDRWIN_DISK_TYPE_HDDVDR = "HD DVD-R"; + /// <summary>DiskType.HDDVDR</summary> + const string CDRWIN_DISK_TYPE_HDDVDRDL = "HD DVD-R DL"; + /// <summary>DiskType.HDDVDRW</summary> + const string CDRWIN_DISK_TYPE_HDDVDRW = "HD DVD-RW"; + /// <summary>DiskType.HDDVDRW</summary> + const string CDRWIN_DISK_TYPE_HDDVDRWDL = "HD DVD-RW DL"; + /// <summary>DiskType.BDROM</summary> + const string CDRWIN_DISK_TYPE_BD = "BD"; + /// <summary>DiskType.BDR</summary> + const string CDRWIN_DISK_TYPE_BDR = "BD-R"; + /// <summary>DiskType.BDRE</summary> + const string CDRWIN_DISK_TYPE_BDRE = "BD-RE"; + /// <summary>DiskType.BDR</summary> + const string CDRWIN_DISK_TYPE_BDRDL = "BD-R DL"; + /// <summary>DiskType.BDRE</summary> + const string CDRWIN_DISK_TYPE_BDREDL = "BD-RE DL"; + + const string REGEX_SESSION = @"\bREM\s+SESSION\s+(?<number>\d+).*$"; + const string REGEX_MEDIA_TYPE = @"\bREM\s+ORIGINAL MEDIA-TYPE:\s+(?<mediatype>.+)$"; + const string REGEX_LEAD_OUT = @"\bREM\s+LEAD-OUT\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; + // Not checked + const string REGEX_LBA = @"\bREM MSF:\s+(?<msf>[\d]+:[\d]+:[\d]+)\s+=\s+LBA:\s+(?<lba>[\d]+)$"; + const string REGEX_DISC_ID = @"\bDISC_ID\s+(?<diskid>[\da-f]{8})$"; + const string REGEX_BARCODE = @"\bUPC_EAN\s+(?<barcode>[\d]{12,13})$"; + const string REGEX_COMMENT = @"\bREM\s+(?<comment>.+)$"; + const string REGEX_CDTEXT = @"\bCDTEXTFILE\s+(?<filename>.+)$"; + const string REGEX_MCN = @"^\s*CATALOG\s*(?<catalog>[\x21-\x7F]{13})$"; + const string REGEX_TITLE = @"\bTITLE\s+(?<title>.+)$"; + const string REGEX_GENRE = @"\bGENRE\s+(?<genre>.+)$"; + const string REGEX_ARRANGER = @"\bARRANGER\s+(?<arranger>.+)$"; + const string REGEX_COMPOSER = @"\bCOMPOSER\s+(?<composer>.+)$"; + const string REGEX_PERFORMER = @"\bPERFORMER\s+(?<performer>.+)$"; + const string REGEX_SONGWRITER = @"\bSONGWRITER\s+(?<songwriter>.+)$"; + const string REGEX_FILE = @"\bFILE\s+(?<filename>.+)\s+(?<type>\S+)$"; + const string REGEX_TRACK = @"\bTRACK\s+(?<number>\d+)\s+(?<type>\S+)$"; + const string REGEX_ISRC = @"\bISRC\s+(?<isrc>\w{12})$"; + const string REGEX_INDEX = @"\bINDEX\s+(?<index>\d+)\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; + const string REGEX_PREGAP = @"\bPREGAP\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; + const string REGEX_POSTGAP = @"\bPOSTGAP\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; + const string REGEX_FLAGS = @"\bFLAGS\s+(((?<dcp>DCP)|(?<quad>4CH)|(?<pre>PRE)|(?<scms>SCMS))\s*)+$"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/Helpers.cs b/DiscImageChef.DiscImages/CDRWin/Helpers.cs new file mode 100644 index 000000000..9d9642adb --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/Helpers.cs @@ -0,0 +1,242 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class CdrWin + { + static ulong CdrWinMsftoLba(string msf) + { + string[] msfElements = msf.Split(':'); + ulong minute = ulong.Parse(msfElements[0]); + ulong second = ulong.Parse(msfElements[1]); + ulong frame = ulong.Parse(msfElements[2]); + + ulong sectors = minute * 60 * 75 + second * 75 + frame; + + return sectors; + } + + static ushort CdrWinTrackTypeToBytesPerSector(string trackType) + { + switch(trackType) + { + case CDRWIN_TRACK_TYPE_MODE1: + case CDRWIN_TRACK_TYPE_MODE2_FORM1: return 2048; + case CDRWIN_TRACK_TYPE_MODE2_FORM2: return 2324; + case CDRWIN_TRACK_TYPE_MODE2_FORMLESS: + case CDRWIN_TRACK_TYPE_CDI: return 2336; + case CDRWIN_TRACK_TYPE_AUDIO: + case CDRWIN_TRACK_TYPE_MODE1_RAW: + case CDRWIN_TRACK_TYPE_MODE2_RAW: + case CDRWIN_TRACK_TYPE_CDG: + case CDRWIN_TRACK_TYPE_CDI_RAW: return 2352; + default: return 0; + } + } + + static ushort CdrWinTrackTypeToCookedBytesPerSector(string trackType) + { + switch(trackType) + { + case CDRWIN_TRACK_TYPE_MODE1: + case CDRWIN_TRACK_TYPE_MODE2_FORM1: + case CDRWIN_TRACK_TYPE_MODE1_RAW: return 2048; + case CDRWIN_TRACK_TYPE_MODE2_FORM2: return 2324; + case CDRWIN_TRACK_TYPE_MODE2_FORMLESS: + case CDRWIN_TRACK_TYPE_CDI: + case CDRWIN_TRACK_TYPE_MODE2_RAW: + case CDRWIN_TRACK_TYPE_CDI_RAW: return 2336; + case CDRWIN_TRACK_TYPE_CDG: + case CDRWIN_TRACK_TYPE_AUDIO: return 2352; + default: return 0; + } + } + + static TrackType CdrWinTrackTypeToTrackType(string trackType) + { + switch(trackType) + { + case CDRWIN_TRACK_TYPE_MODE1: + case CDRWIN_TRACK_TYPE_MODE1_RAW: return TrackType.CdMode1; + case CDRWIN_TRACK_TYPE_MODE2_FORM1: return TrackType.CdMode2Form1; + case CDRWIN_TRACK_TYPE_MODE2_FORM2: return TrackType.CdMode2Form2; + case CDRWIN_TRACK_TYPE_CDI_RAW: + case CDRWIN_TRACK_TYPE_CDI: + case CDRWIN_TRACK_TYPE_MODE2_RAW: + case CDRWIN_TRACK_TYPE_MODE2_FORMLESS: return TrackType.CdMode2Formless; + case CDRWIN_TRACK_TYPE_AUDIO: + case CDRWIN_TRACK_TYPE_CDG: return TrackType.Audio; + default: return TrackType.Data; + } + } + + static MediaType CdrWinIsoBusterDiscTypeToMediaType(string discType) + { + switch(discType) + { + case CDRWIN_DISK_TYPE_CD: return MediaType.CD; + case CDRWIN_DISK_TYPE_CDRW: + case CDRWIN_DISK_TYPE_CDMRW: + case CDRWIN_DISK_TYPE_CDMRW2: return MediaType.CDRW; + case CDRWIN_DISK_TYPE_DVD: return MediaType.DVDROM; + case CDRWIN_DISK_TYPE_DVDPRW: + case CDRWIN_DISK_TYPE_DVDPMRW: + case CDRWIN_DISK_TYPE_DVDPMRW2: return MediaType.DVDPRW; + case CDRWIN_DISK_TYPE_DVDPRWDL: + case CDRWIN_DISK_TYPE_DVDPMRWDL: + case CDRWIN_DISK_TYPE_DVDPMRWDL2: return MediaType.DVDPRWDL; + case CDRWIN_DISK_TYPE_DVDPR: + case CDRWIN_DISK_TYPE_DVDPVR: return MediaType.DVDPR; + case CDRWIN_DISK_TYPE_DVDPRDL: return MediaType.DVDPRDL; + case CDRWIN_DISK_TYPE_DVDRAM: return MediaType.DVDRAM; + case CDRWIN_DISK_TYPE_DVDVR: + case CDRWIN_DISK_TYPE_DVDR: return MediaType.DVDR; + case CDRWIN_DISK_TYPE_DVDRDL: return MediaType.DVDRDL; + case CDRWIN_DISK_TYPE_DVDRW: + case CDRWIN_DISK_TYPE_DVDRWDL: + case CDRWIN_DISK_TYPE_DVDRW2: return MediaType.DVDRW; + case CDRWIN_DISK_TYPE_HDDVD: return MediaType.HDDVDROM; + case CDRWIN_DISK_TYPE_HDDVDRAM: return MediaType.HDDVDRAM; + case CDRWIN_DISK_TYPE_HDDVDR: + case CDRWIN_DISK_TYPE_HDDVDRDL: return MediaType.HDDVDR; + case CDRWIN_DISK_TYPE_HDDVDRW: + case CDRWIN_DISK_TYPE_HDDVDRWDL: return MediaType.HDDVDRW; + case CDRWIN_DISK_TYPE_BD: return MediaType.BDROM; + case CDRWIN_DISK_TYPE_BDR: + case CDRWIN_DISK_TYPE_BDRDL: return MediaType.BDR; + case CDRWIN_DISK_TYPE_BDRE: + case CDRWIN_DISK_TYPE_BDREDL: return MediaType.BDRE; + default: return MediaType.Unknown; + } + } + + static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) + { + return ((byte)(sector / 75 / 60), (byte)(sector / 75 % 60), (byte)(sector % 75)); + } + + static string GetTrackMode(Track track) + { + switch(track.TrackType) + { + case TrackType.Audio when track.TrackRawBytesPerSector == 2352: return CDRWIN_TRACK_TYPE_AUDIO; + case TrackType.Data: return CDRWIN_TRACK_TYPE_MODE1; + case TrackType.CdMode1 when track.TrackRawBytesPerSector == 2352: return CDRWIN_TRACK_TYPE_MODE1_RAW; + case TrackType.CdMode2Formless + when track.TrackRawBytesPerSector != 2352: return CDRWIN_TRACK_TYPE_MODE2_FORMLESS; + case TrackType.CdMode2Form1 + when track.TrackRawBytesPerSector != 2352: return CDRWIN_TRACK_TYPE_MODE2_FORM1; + case TrackType.CdMode2Form2 + when track.TrackRawBytesPerSector != 2352: return CDRWIN_TRACK_TYPE_MODE2_FORM2; + case TrackType.CdMode2Formless when track.TrackRawBytesPerSector == 2352: + case TrackType.CdMode2Form1 when track.TrackRawBytesPerSector == 2352: + case TrackType.CdMode2Form2 + when track.TrackRawBytesPerSector == 2352: return CDRWIN_TRACK_TYPE_MODE2_RAW; + default: return CDRWIN_TRACK_TYPE_MODE1; + } + } + + static string MediaTypeToCdrwinType(MediaType type) + { + switch(type) + { + case MediaType.BDRXL: + case MediaType.BDR: return CDRWIN_DISK_TYPE_BDR; + case MediaType.BDREXL: + case MediaType.BDRE: return CDRWIN_DISK_TYPE_BDRE; + case MediaType.BDROM: + case MediaType.CBHD: + case MediaType.PS3BD: + case MediaType.PS4BD: + case MediaType.UDO: + case MediaType.UDO2: + case MediaType.UDO2_WORM: return CDRWIN_DISK_TYPE_BD; + case MediaType.CDV: + case MediaType.DDCD: + case MediaType.DDCDR: + case MediaType.DDCDRW: + case MediaType.CDPLUS: + case MediaType.CDR: + case MediaType.CDROM: + case MediaType.CDROMXA: + case MediaType.CD: + case MediaType.CDDA: + case MediaType.CDEG: + case MediaType.CDG: + case MediaType.CDI: + case MediaType.CDMIDI: + case MediaType.DTSCD: + case MediaType.JaguarCD: + case MediaType.MEGACD: + case MediaType.PD650: + case MediaType.PD650_WORM: + case MediaType.PS1CD: + case MediaType.PS2CD: + case MediaType.SuperCDROM2: + case MediaType.SVCD: + case MediaType.SVOD: + case MediaType.SATURNCD: + case MediaType.ThreeDO: + case MediaType.VCD: + case MediaType.VCDHD: return CDRWIN_DISK_TYPE_CD; + case MediaType.CDMRW: return CDRWIN_DISK_TYPE_CDMRW; + case MediaType.CDRW: return CDRWIN_DISK_TYPE_CDRW; + case MediaType.DVDPR: return CDRWIN_DISK_TYPE_DVDPR; + case MediaType.DVDPRDL: return CDRWIN_DISK_TYPE_DVDPRDL; + case MediaType.DVDPRW: return CDRWIN_DISK_TYPE_DVDPRW; + case MediaType.DVDPRWDL: return CDRWIN_DISK_TYPE_DVDPRWDL; + case MediaType.DVDR: return CDRWIN_DISK_TYPE_DVDR; + case MediaType.DVDRAM: return CDRWIN_DISK_TYPE_DVDRAM; + case MediaType.DVDRDL: return CDRWIN_DISK_TYPE_DVDRDL; + case MediaType.DVDDownload: + case MediaType.DVDROM: + case MediaType.UMD: + case MediaType.PS2DVD: + case MediaType.PS3DVD: return CDRWIN_DISK_TYPE_DVD; + case MediaType.DVDRW: return CDRWIN_DISK_TYPE_DVDRW; + case MediaType.DVDRWDL: return CDRWIN_DISK_TYPE_DVDRWDL; + case MediaType.HDDVDR: return CDRWIN_DISK_TYPE_HDDVDR; + case MediaType.HDDVDRAM: return CDRWIN_DISK_TYPE_HDDVDRAM; + case MediaType.HDDVDRDL: return CDRWIN_DISK_TYPE_HDDVDRDL; + case MediaType.HDDVDROM: return CDRWIN_DISK_TYPE_HDDVD; + case MediaType.HDDVDRW: return CDRWIN_DISK_TYPE_HDDVDRW; + case MediaType.HDDVDRWDL: return CDRWIN_DISK_TYPE_HDDVDRWDL; + default: return ""; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/Identify.cs b/DiscImageChef.DiscImages/CDRWin/Identify.cs new file mode 100644 index 000000000..db4e8123f --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/Identify.cs @@ -0,0 +1,105 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class CdrWin + { + // Due to .cue format, this method must parse whole file, ignoring errors (those will be thrown by OpenImage()). + public bool Identify(IFilter imageFilter) + { + cdrwinFilter = imageFilter; + + try + { + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + byte[] testArray = new byte[512]; + imageFilter.GetDataForkStream().Read(testArray, 0, 512); + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin + bool twoConsecutiveNulls = false; + for(int i = 0; i < 512; i++) + { + if(i >= imageFilter.GetDataForkStream().Length) break; + + if(testArray[i] == 0) + { + if(twoConsecutiveNulls) return false; + + twoConsecutiveNulls = true; + } + else twoConsecutiveNulls = false; + + if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) + return false; + } + + cueStream = new StreamReader(cdrwinFilter.GetDataForkStream()); + + while(cueStream.Peek() >= 0) + { + string line = cueStream.ReadLine(); + + Regex sr = new Regex(REGEX_SESSION); + Regex rr = new Regex(REGEX_COMMENT); + Regex cr = new Regex(REGEX_MCN); + Regex fr = new Regex(REGEX_FILE); + Regex tr = new Regex(REGEX_CDTEXT); + + // First line must be SESSION, REM, CATALOG, FILE or CDTEXTFILE. + Match sm = sr.Match(line ?? throw new InvalidOperationException()); + Match rm = rr.Match(line); + Match cm = cr.Match(line); + Match fm = fr.Match(line); + Match tm = tr.Match(line); + + return sm.Success || rm.Success || cm.Success || fm.Success || tm.Success; + } + + return false; + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", cdrwinFilter); + DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); + DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); + return false; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/Properties.cs b/DiscImageChef.DiscImages/CDRWin/Properties.cs new file mode 100644 index 000000000..695ab6c81 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/Properties.cs @@ -0,0 +1,129 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class CdrWin + { + public ImageInfo Info => imageInfo; + public string Name => "CDRWin cuesheet"; + public Guid Id => new Guid("664568B2-15D4-4E64-8A7A-20BDA8B8386F"); + public string Format => "CDRWin CUESheet"; + public List<Partition> Partitions { get; private set; } + public List<Track> Tracks + { + get + { + List<Track> tracks = new List<Track>(); + + ulong previousStartSector = 0; + + foreach(CdrWinTrack cdrTrack in discimage.Tracks) + { + Track dicTrack = new Track + { + Indexes = cdrTrack.Indexes, + TrackDescription = cdrTrack.Title, + TrackStartSector = previousStartSector, + TrackPregap = cdrTrack.Pregap, + TrackSession = cdrTrack.Session, + TrackSequence = cdrTrack.Sequence, + TrackType = CdrWinTrackTypeToTrackType(cdrTrack.Tracktype), + TrackFile = cdrTrack.Trackfile.Datafilter.GetFilename(), + TrackFilter = cdrTrack.Trackfile.Datafilter, + TrackFileOffset = cdrTrack.Trackfile.Offset, + TrackFileType = cdrTrack.Trackfile.Filetype, + TrackRawBytesPerSector = cdrTrack.Bps, + TrackBytesPerSector = CdrWinTrackTypeToCookedBytesPerSector(cdrTrack.Tracktype) + }; + dicTrack.TrackEndSector = dicTrack.TrackStartSector + cdrTrack.Sectors - 1; + + /*if(!cdrTrack.Indexes.TryGetValue(0, out dicTrack.TrackStartSector)) + cdrTrack.Indexes.TryGetValue(1, out dicTrack.TrackStartSector);*/ + if(cdrTrack.Tracktype == CDRWIN_TRACK_TYPE_CDG) + { + dicTrack.TrackSubchannelFilter = cdrTrack.Trackfile.Datafilter; + dicTrack.TrackSubchannelFile = cdrTrack.Trackfile.Datafilter.GetFilename(); + dicTrack.TrackSubchannelOffset = cdrTrack.Trackfile.Offset; + dicTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + } + else dicTrack.TrackSubchannelType = TrackSubchannelType.None; + + tracks.Add(dicTrack); + previousStartSector = dicTrack.TrackEndSector + 1; + } + + return tracks; + } + } + public List<Session> Sessions => discimage.Sessions; + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.CD_MCN, MediaTagType.CD_TEXT}; + public IEnumerable<SectorTagType> SupportedSectorTags => + new[] + { + SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, + SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubHeader, + SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, SectorTagType.CdTrackIsrc + }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.BDR, MediaType.BDRE, MediaType.BDREXL, MediaType.BDROM, MediaType.BDRXL, MediaType.CBHD, + MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, + MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, + MediaType.CDV, MediaType.DDCD, MediaType.DDCDR, MediaType.DDCDRW, MediaType.DVDDownload, + MediaType.DVDPR, MediaType.DVDPRDL, MediaType.DVDPRW, MediaType.DVDPRWDL, MediaType.DVDR, + MediaType.DVDRAM, MediaType.DVDRDL, MediaType.DVDROM, MediaType.DVDRW, MediaType.DVDRWDL, MediaType.EVD, + MediaType.FDDVD, MediaType.DTSCD, MediaType.FVD, MediaType.HDDVDR, MediaType.HDDVDRAM, + MediaType.HDDVDRDL, MediaType.HDDVDROM, MediaType.HDDVDRW, MediaType.HDDVDRWDL, MediaType.HDVMD, + MediaType.HVD, MediaType.JaguarCD, MediaType.MEGACD, MediaType.PD650, MediaType.PD650_WORM, + MediaType.PS1CD, MediaType.PS2CD, MediaType.PS2DVD, MediaType.PS3BD, MediaType.PS3DVD, MediaType.PS4BD, + MediaType.SuperCDROM2, MediaType.SVCD, MediaType.SVOD, MediaType.SATURNCD, MediaType.ThreeDO, + MediaType.UDO, MediaType.UDO2, MediaType.UDO2_WORM, MediaType.UMD, MediaType.VCD, MediaType.VCDHD, + MediaType.NeoGeoCD, MediaType.PCFX + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new[] {("separate", typeof(bool), "Write each track to a separate file.")}; + public IEnumerable<string> KnownExtensions => new[] {".cue"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin.cs b/DiscImageChef.DiscImages/CDRWin/Read.cs similarity index 62% rename from DiscImageChef.DiscImages/CDRWin.cs rename to DiscImageChef.DiscImages/CDRWin/Read.cs index a48faac48..99642431a 100644 --- a/DiscImageChef.DiscImages/CDRWin.cs +++ b/DiscImageChef.DiscImages/CDRWin/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : CDRWin.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages CDRWin cuesheets (cue/bin). +// Reads CDRWin cuesheets (cue/bin). // // --[ License ] -------------------------------------------------------------- // @@ -43,299 +43,11 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // TODO: Implement track flags - public class CdrWin : IWritableImage + public partial class CdrWin { - // Type for FILE entity - /// <summary>Data as-is in little-endian</summary> - const string CDRWIN_DISK_TYPE_LITTLE_ENDIAN = "BINARY"; - /// <summary>Data as-is in big-endian</summary> - const string CDRWIN_DISK_TYPE_BIG_ENDIAN = "MOTOROLA"; - /// <summary>Audio in Apple AIF file</summary> - const string CDRWIN_DISK_TYPE_AIFF = "AIFF"; - /// <summary>Audio in Microsoft WAV file</summary> - const string CDRWIN_DISK_TYPE_RIFF = "WAVE"; - /// <summary>Audio in MP3 file</summary> - const string CDRWIN_DISK_TYPE_MP3 = "MP3"; - - // Type for TRACK entity - /// <summary>Audio track, 2352 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_AUDIO = "AUDIO"; - /// <summary>CD+G track, 2448 bytes/sector (audio+subchannel)</summary> - const string CDRWIN_TRACK_TYPE_CDG = "CDG"; - /// <summary>Mode 1 track, cooked, 2048 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_MODE1 = "MODE1/2048"; - /// <summary>Mode 1 track, raw, 2352 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_MODE1_RAW = "MODE1/2352"; - /// <summary>Mode 2 form 1 track, cooked, 2048 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_MODE2_FORM1 = "MODE2/2048"; - /// <summary>Mode 2 form 2 track, cooked, 2324 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_MODE2_FORM2 = "MODE2/2324"; - /// <summary>Mode 2 formless track, cooked, 2336 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_MODE2_FORMLESS = "MODE2/2336"; - /// <summary>Mode 2 track, raw, 2352 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_MODE2_RAW = "MODE2/2352"; - /// <summary>CD-i track, cooked, 2336 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_CDI = "CDI/2336"; - /// <summary>CD-i track, raw, 2352 bytes/sector</summary> - const string CDRWIN_TRACK_TYPE_CDI_RAW = "CDI/2352"; - - // Type for REM ORIGINAL MEDIA-TYPE entity - /// <summary>DiskType.CD</summary> - const string CDRWIN_DISK_TYPE_CD = "CD"; - /// <summary>DiskType.CDRW</summary> - const string CDRWIN_DISK_TYPE_CDRW = "CD-RW"; - /// <summary>DiskType.CDMRW</summary> - const string CDRWIN_DISK_TYPE_CDMRW = "CD-MRW"; - /// <summary>DiskType.CDMRW</summary> - const string CDRWIN_DISK_TYPE_CDMRW2 = "CD-(MRW)"; - /// <summary>DiskType.DVDROM</summary> - const string CDRWIN_DISK_TYPE_DVD = "DVD"; - /// <summary>DiskType.DVDPRW</summary> - const string CDRWIN_DISK_TYPE_DVDPMRW = "DVD+MRW"; - /// <summary>DiskType.DVDPRW</summary> - const string CDRWIN_DISK_TYPE_DVDPMRW2 = "DVD+(MRW)"; - /// <summary>DiskType.DVDPRWDL</summary> - const string CDRWIN_DISK_TYPE_DVDPMRWDL = "DVD+MRW DL"; - /// <summary>DiskType.DVDPRWDL</summary> - const string CDRWIN_DISK_TYPE_DVDPMRWDL2 = "DVD+(MRW) DL"; - /// <summary>DiskType.DVDPR</summary> - const string CDRWIN_DISK_TYPE_DVDPR = "DVD+R"; - /// <summary>DiskType.DVDPRDL</summary> - const string CDRWIN_DISK_TYPE_DVDPRDL = "DVD+R DL"; - /// <summary>DiskType.DVDPRW</summary> - const string CDRWIN_DISK_TYPE_DVDPRW = "DVD+RW"; - /// <summary>DiskType.DVDPRWDL</summary> - const string CDRWIN_DISK_TYPE_DVDPRWDL = "DVD+RW DL"; - /// <summary>DiskType.DVDPR</summary> - const string CDRWIN_DISK_TYPE_DVDPVR = "DVD+VR"; - /// <summary>DiskType.DVDRAM</summary> - const string CDRWIN_DISK_TYPE_DVDRAM = "DVD-RAM"; - /// <summary>DiskType.DVDR</summary> - const string CDRWIN_DISK_TYPE_DVDR = "DVD-R"; - /// <summary>DiskType.DVDRDL</summary> - const string CDRWIN_DISK_TYPE_DVDRDL = "DVD-R DL"; - /// <summary>DiskType.DVDRW</summary> - const string CDRWIN_DISK_TYPE_DVDRW = "DVD-RW"; - /// <summary>DiskType.DVDRWDL</summary> - const string CDRWIN_DISK_TYPE_DVDRWDL = "DVD-RW DL"; - /// <summary>DiskType.DVDR</summary> - const string CDRWIN_DISK_TYPE_DVDVR = "DVD-VR"; - /// <summary>DiskType.DVDRW</summary> - const string CDRWIN_DISK_TYPE_DVDRW2 = "DVDRW"; - /// <summary>DiskType.HDDVDROM</summary> - const string CDRWIN_DISK_TYPE_HDDVD = "HD DVD"; - /// <summary>DiskType.HDDVDRAM</summary> - const string CDRWIN_DISK_TYPE_HDDVDRAM = "HD DVD-RAM"; - /// <summary>DiskType.HDDVDR</summary> - const string CDRWIN_DISK_TYPE_HDDVDR = "HD DVD-R"; - /// <summary>DiskType.HDDVDR</summary> - const string CDRWIN_DISK_TYPE_HDDVDRDL = "HD DVD-R DL"; - /// <summary>DiskType.HDDVDRW</summary> - const string CDRWIN_DISK_TYPE_HDDVDRW = "HD DVD-RW"; - /// <summary>DiskType.HDDVDRW</summary> - const string CDRWIN_DISK_TYPE_HDDVDRWDL = "HD DVD-RW DL"; - /// <summary>DiskType.BDROM</summary> - const string CDRWIN_DISK_TYPE_BD = "BD"; - /// <summary>DiskType.BDR</summary> - const string CDRWIN_DISK_TYPE_BDR = "BD-R"; - /// <summary>DiskType.BDRE</summary> - const string CDRWIN_DISK_TYPE_BDRE = "BD-RE"; - /// <summary>DiskType.BDR</summary> - const string CDRWIN_DISK_TYPE_BDRDL = "BD-R DL"; - /// <summary>DiskType.BDRE</summary> - const string CDRWIN_DISK_TYPE_BDREDL = "BD-RE DL"; - - const string REGEX_SESSION = @"\bREM\s+SESSION\s+(?<number>\d+).*$"; - const string REGEX_MEDIA_TYPE = @"\bREM\s+ORIGINAL MEDIA-TYPE:\s+(?<mediatype>.+)$"; - const string REGEX_LEAD_OUT = @"\bREM\s+LEAD-OUT\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; - // Not checked - const string REGEX_LBA = @"\bREM MSF:\s+(?<msf>[\d]+:[\d]+:[\d]+)\s+=\s+LBA:\s+(?<lba>[\d]+)$"; - const string REGEX_DISC_ID = @"\bDISC_ID\s+(?<diskid>[\da-f]{8})$"; - const string REGEX_BARCODE = @"\bUPC_EAN\s+(?<barcode>[\d]{12,13})$"; - const string REGEX_COMMENT = @"\bREM\s+(?<comment>.+)$"; - const string REGEX_CDTEXT = @"\bCDTEXTFILE\s+(?<filename>.+)$"; - const string REGEX_MCN = @"^\s*CATALOG\s*(?<catalog>[\x21-\x7F]{13})$"; - const string REGEX_TITLE = @"\bTITLE\s+(?<title>.+)$"; - const string REGEX_GENRE = @"\bGENRE\s+(?<genre>.+)$"; - const string REGEX_ARRANGER = @"\bARRANGER\s+(?<arranger>.+)$"; - const string REGEX_COMPOSER = @"\bCOMPOSER\s+(?<composer>.+)$"; - const string REGEX_PERFORMER = @"\bPERFORMER\s+(?<performer>.+)$"; - const string REGEX_SONGWRITER = @"\bSONGWRITER\s+(?<songwriter>.+)$"; - const string REGEX_FILE = @"\bFILE\s+(?<filename>.+)\s+(?<type>\S+)$"; - const string REGEX_TRACK = @"\bTRACK\s+(?<number>\d+)\s+(?<type>\S+)$"; - const string REGEX_ISRC = @"\bISRC\s+(?<isrc>\w{12})$"; - const string REGEX_INDEX = @"\bINDEX\s+(?<index>\d+)\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; - const string REGEX_PREGAP = @"\bPREGAP\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; - const string REGEX_POSTGAP = @"\bPOSTGAP\s+(?<msf>[\d]+:[\d]+:[\d]+)$"; - const string REGEX_FLAGS = @"\bFLAGS\s+(((?<dcp>DCP)|(?<quad>4CH)|(?<pre>PRE)|(?<scms>SCMS))\s*)+$"; - - IFilter cdrwinFilter; - StreamReader cueStream; - StreamWriter descriptorStream; - CdrWinDisc discimage; - ImageInfo imageInfo; - Stream imageStream; - /// <summary>Dictionary, index is track #, value is TrackFile</summary> - Dictionary<uint, ulong> offsetmap; - bool separateTracksWriting; - - Dictionary<byte, byte> trackFlags; - Dictionary<byte, string> trackIsrcs; - string writingBaseName; - Dictionary<uint, FileStream> writingStreams; - List<Track> writingTracks; - - public CdrWin() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = true, - HasSessions = true, - Version = null, - ApplicationVersion = null, - MediaTitle = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "CDRWin cuesheet"; - public Guid Id => new Guid("664568B2-15D4-4E64-8A7A-20BDA8B8386F"); - - public string Format => "CDRWin CUESheet"; - - public List<Partition> Partitions { get; private set; } - - public List<Track> Tracks - { - get - { - List<Track> tracks = new List<Track>(); - - ulong previousStartSector = 0; - - foreach(CdrWinTrack cdrTrack in discimage.Tracks) - { - Track dicTrack = new Track - { - Indexes = cdrTrack.Indexes, - TrackDescription = cdrTrack.Title, - TrackStartSector = previousStartSector, - TrackPregap = cdrTrack.Pregap, - TrackSession = cdrTrack.Session, - TrackSequence = cdrTrack.Sequence, - TrackType = CdrWinTrackTypeToTrackType(cdrTrack.Tracktype), - TrackFile = cdrTrack.Trackfile.Datafilter.GetFilename(), - TrackFilter = cdrTrack.Trackfile.Datafilter, - TrackFileOffset = cdrTrack.Trackfile.Offset, - TrackFileType = cdrTrack.Trackfile.Filetype, - TrackRawBytesPerSector = cdrTrack.Bps, - TrackBytesPerSector = CdrWinTrackTypeToCookedBytesPerSector(cdrTrack.Tracktype) - }; - dicTrack.TrackEndSector = dicTrack.TrackStartSector + cdrTrack.Sectors - 1; - - /*if(!cdrTrack.Indexes.TryGetValue(0, out dicTrack.TrackStartSector)) - cdrTrack.Indexes.TryGetValue(1, out dicTrack.TrackStartSector);*/ - if(cdrTrack.Tracktype == CDRWIN_TRACK_TYPE_CDG) - { - dicTrack.TrackSubchannelFilter = cdrTrack.Trackfile.Datafilter; - dicTrack.TrackSubchannelFile = cdrTrack.Trackfile.Datafilter.GetFilename(); - dicTrack.TrackSubchannelOffset = cdrTrack.Trackfile.Offset; - dicTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; - } - else dicTrack.TrackSubchannelType = TrackSubchannelType.None; - - tracks.Add(dicTrack); - previousStartSector = dicTrack.TrackEndSector + 1; - } - - return tracks; - } - } - - public List<Session> Sessions => discimage.Sessions; - - // Due to .cue format, this method must parse whole file, ignoring errors (those will be thrown by OpenImage()). - public bool Identify(IFilter imageFilter) - { - cdrwinFilter = imageFilter; - - try - { - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - byte[] testArray = new byte[512]; - imageFilter.GetDataForkStream().Read(testArray, 0, 512); - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin - bool twoConsecutiveNulls = false; - for(int i = 0; i < 512; i++) - { - if(i >= imageFilter.GetDataForkStream().Length) break; - - if(testArray[i] == 0) - { - if(twoConsecutiveNulls) return false; - - twoConsecutiveNulls = true; - } - else twoConsecutiveNulls = false; - - if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) - return false; - } - - cueStream = new StreamReader(cdrwinFilter.GetDataForkStream()); - - while(cueStream.Peek() >= 0) - { - string line = cueStream.ReadLine(); - - Regex sr = new Regex(REGEX_SESSION); - Regex rr = new Regex(REGEX_COMMENT); - Regex cr = new Regex(REGEX_MCN); - Regex fr = new Regex(REGEX_FILE); - Regex tr = new Regex(REGEX_CDTEXT); - - // First line must be SESSION, REM, CATALOG, FILE or CDTEXTFILE. - Match sm = sr.Match(line ?? throw new InvalidOperationException()); - Match rm = rr.Match(line); - Match cm = cr.Match(line); - Match fm = fr.Match(line); - Match tm = tr.Match(line); - - return sm.Success || rm.Success || cm.Success || fm.Success || tm.Success; - } - - return false; - } - catch(Exception ex) - { - DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", cdrwinFilter); - DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); - DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); - return false; - } - } - public bool Open(IFilter imageFilter) { if(imageFilter == null) return false; @@ -1806,796 +1518,5 @@ namespace DiscImageChef.DiscImages return failingLbas.Count <= 0; } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.CD_MCN, MediaTagType.CD_TEXT}; - public IEnumerable<SectorTagType> SupportedSectorTags => - new[] - { - SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, - SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubHeader, - SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, SectorTagType.CdTrackIsrc - }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.BDR, MediaType.BDRE, MediaType.BDREXL, MediaType.BDROM, MediaType.BDRXL, MediaType.CBHD, - MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, - MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, - MediaType.CDV, MediaType.DDCD, MediaType.DDCDR, MediaType.DDCDRW, MediaType.DVDDownload, - MediaType.DVDPR, MediaType.DVDPRDL, MediaType.DVDPRW, MediaType.DVDPRWDL, MediaType.DVDR, - MediaType.DVDRAM, MediaType.DVDRDL, MediaType.DVDROM, MediaType.DVDRW, MediaType.DVDRWDL, MediaType.EVD, - MediaType.FDDVD, MediaType.DTSCD, MediaType.FVD, MediaType.HDDVDR, MediaType.HDDVDRAM, - MediaType.HDDVDRDL, MediaType.HDDVDROM, MediaType.HDDVDRW, MediaType.HDDVDRWDL, MediaType.HDVMD, - MediaType.HVD, MediaType.JaguarCD, MediaType.MEGACD, MediaType.PD650, MediaType.PD650_WORM, - MediaType.PS1CD, MediaType.PS2CD, MediaType.PS2DVD, MediaType.PS3BD, MediaType.PS3DVD, MediaType.PS4BD, - MediaType.SuperCDROM2, MediaType.SVCD, MediaType.SVOD, MediaType.SATURNCD, MediaType.ThreeDO, - MediaType.UDO, MediaType.UDO2, MediaType.UDO2_WORM, MediaType.UMD, MediaType.VCD, MediaType.VCDHD, - MediaType.NeoGeoCD, MediaType.PCFX - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new[] {("separate", typeof(bool), "Write each track to a separate file.")}; - public IEnumerable<string> KnownExtensions => new[] {".cue"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(options != null) - { - if(options.TryGetValue("separate", out string tmpValue)) - { - if(!bool.TryParse(tmpValue, out separateTracksWriting)) - { - ErrorMessage = "Invalid value for split option"; - return false; - } - - if(separateTracksWriting) - { - ErrorMessage = "Separate tracksnot yet implemented"; - return false; - } - } - } - else separateTracksWriting = false; - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - // TODO: Separate tracks - try - { - writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); - descriptorStream = new StreamWriter(path, false, Encoding.ASCII); - } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - discimage = new CdrWinDisc - { - Disktype = mediaType, - Sessions = new List<Session>(), - Tracks = new List<CdrWinTrack>() - }; - - trackFlags = new Dictionary<byte, byte>(); - trackIsrcs = new Dictionary<byte, string>(); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - switch(tag) - { - case MediaTagType.CD_MCN: - discimage.Mcn = Encoding.ASCII.GetString(data); - return true; - case MediaTagType.CD_TEXT: - FileStream cdTextStream = new FileStream(writingBaseName + "_cdtext.bin", FileMode.Create, - FileAccess.ReadWrite, FileShare.None); - cdTextStream.Write(data, 0, data.Length); - discimage.Cdtextfile = Path.GetFileName(cdTextStream.Name); - cdTextStream.Close(); - return true; - default: - ErrorMessage = $"Unsupported media tag {tag}"; - return false; - } - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) - { - ErrorMessage = "Invalid write mode for this sector"; - return false; - } - - if(data.Length != track.TrackRawBytesPerSector) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) - { - ErrorMessage = "Invalid write mode for this sector"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - if(data.Length % track.TrackRawBytesPerSector != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(data.Length != track.TrackRawBytesPerSector) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - return true; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; - - if(trackStream == null) - { - ErrorMessage = $"Can't found file containing {sectorAddress}"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - if(data.Length % track.TrackRawBytesPerSector != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - trackStream.Write(data, 0, data.Length); - - return true; - } - - public bool SetTracks(List<Track> tracks) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(tracks == null || tracks.Count == 0) - { - ErrorMessage = "Invalid tracks sent"; - return false; - } - - if(writingTracks != null && writingStreams != null) - foreach(FileStream oldTrack in writingStreams.Select(t => t.Value).Distinct()) - oldTrack.Close(); - - ulong currentOffset = 0; - writingTracks = new List<Track>(); - foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) - { - Track newTrack = track; - newTrack.TrackFile = separateTracksWriting - ? writingBaseName + $"_track{track.TrackSequence:D2}.bin" - : writingBaseName + ".bin"; - newTrack.TrackFileOffset = separateTracksWriting ? 0 : currentOffset; - writingTracks.Add(newTrack); - currentOffset += (ulong)newTrack.TrackRawBytesPerSector * - (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); - } - - writingStreams = new Dictionary<uint, FileStream>(); - if(separateTracksWriting) - foreach(Track track in writingTracks) - writingStreams.Add(track.TrackSequence, - new FileStream(track.TrackFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, - FileShare.None)); - else - { - FileStream jointstream = new FileStream(writingBaseName + ".bin", FileMode.OpenOrCreate, - FileAccess.ReadWrite, FileShare.None); - foreach(Track track in writingTracks) writingStreams.Add(track.TrackSequence, jointstream); - } - - return true; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(separateTracksWriting) - foreach(FileStream writingStream in writingStreams.Values) - { - writingStream.Flush(); - writingStream.Close(); - } - else - { - writingStreams.First().Value.Flush(); - writingStreams.First().Value.Close(); - } - - int currentSession = 0; - - if(!string.IsNullOrWhiteSpace(discimage.Comment)) - { - string[] commentLines = discimage.Comment.Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries); - foreach(string line in commentLines) descriptorStream.WriteLine("REM {0}", line); - } - - descriptorStream.WriteLine("REM ORIGINAL MEDIA-TYPE {0}", MediaTypeToCdrwinType(imageInfo.MediaType)); - - if(!string.IsNullOrEmpty(discimage.Cdtextfile)) - descriptorStream.WriteLine("CDTEXTFILE \"{0}\"", Path.GetFileName(discimage.Cdtextfile)); - - if(!string.IsNullOrEmpty(discimage.Title)) descriptorStream.WriteLine("TITLE {0}", discimage.Title); - - if(!string.IsNullOrEmpty(discimage.Mcn)) descriptorStream.WriteLine("CATALOG {0}", discimage.Mcn); - - if(!string.IsNullOrEmpty(discimage.Barcode)) descriptorStream.WriteLine("UPC_EAN {0}", discimage.Barcode); - - if(!separateTracksWriting) - descriptorStream.WriteLine("FILE \"{0}\" BINARY", Path.GetFileName(writingStreams.First().Value.Name)); - - foreach(Track track in writingTracks) - { - if(track.TrackSession > currentSession) descriptorStream.WriteLine("REM SESSION {0}", ++currentSession); - - if(separateTracksWriting) - descriptorStream.WriteLine("FILE \"{0}\" BINARY", Path.GetFileName(track.TrackFile)); - - (byte minute, byte second, byte frame) msf = LbaToMsf(track.TrackStartSector); - descriptorStream.WriteLine(" TRACK {0:D2} {1}", track.TrackSequence, GetTrackMode(track)); - - if(trackFlags.TryGetValue((byte)track.TrackSequence, out byte flagsByte)) - if(flagsByte != 0 && flagsByte != (byte)CdFlags.DataTrack) - { - CdFlags flags = (CdFlags)flagsByte; - descriptorStream.WriteLine(" FLAGS{0}{1}{2}", - flags.HasFlag(CdFlags.CopyPermitted) ? " DCP" : "", - flags.HasFlag(CdFlags.FourChannel) ? " 4CH" : "", - flags.HasFlag(CdFlags.PreEmphasis) ? " PRE" : ""); - } - - if(trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc)) - descriptorStream.WriteLine(" ISRC {0}", isrc); - - descriptorStream.WriteLine(" INDEX {0:D2} {1:D2}:{2:D2}:{3:D2}", 1, msf.minute, msf.second, - msf.frame); - } - - descriptorStream.Flush(); - descriptorStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - { - if(data.Length != 1) - { - ErrorMessage = "Incorrect data size for track flags"; - return false; - } - - trackFlags.Add((byte)track.TrackSequence, data[0]); - return true; - } - case SectorTagType.CdTrackIsrc: - { - if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); - return true; - } - default: - ErrorMessage = $"Unsupported tag type {tag}"; - return false; - } - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - return WriteSectorTag(data, sectorAddress, tag); - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - public bool SetMetadata(ImageInfo metadata) - { - discimage.Barcode = metadata.MediaBarcode; - discimage.Comment = metadata.Comments; - discimage.Title = metadata.MediaTitle; - return true; - } - - static ulong CdrWinMsftoLba(string msf) - { - string[] msfElements = msf.Split(':'); - ulong minute = ulong.Parse(msfElements[0]); - ulong second = ulong.Parse(msfElements[1]); - ulong frame = ulong.Parse(msfElements[2]); - - ulong sectors = minute * 60 * 75 + second * 75 + frame; - - return sectors; - } - - static ushort CdrWinTrackTypeToBytesPerSector(string trackType) - { - switch(trackType) - { - case CDRWIN_TRACK_TYPE_MODE1: - case CDRWIN_TRACK_TYPE_MODE2_FORM1: return 2048; - case CDRWIN_TRACK_TYPE_MODE2_FORM2: return 2324; - case CDRWIN_TRACK_TYPE_MODE2_FORMLESS: - case CDRWIN_TRACK_TYPE_CDI: return 2336; - case CDRWIN_TRACK_TYPE_AUDIO: - case CDRWIN_TRACK_TYPE_MODE1_RAW: - case CDRWIN_TRACK_TYPE_MODE2_RAW: - case CDRWIN_TRACK_TYPE_CDG: - case CDRWIN_TRACK_TYPE_CDI_RAW: return 2352; - default: return 0; - } - } - - static ushort CdrWinTrackTypeToCookedBytesPerSector(string trackType) - { - switch(trackType) - { - case CDRWIN_TRACK_TYPE_MODE1: - case CDRWIN_TRACK_TYPE_MODE2_FORM1: - case CDRWIN_TRACK_TYPE_MODE1_RAW: return 2048; - case CDRWIN_TRACK_TYPE_MODE2_FORM2: return 2324; - case CDRWIN_TRACK_TYPE_MODE2_FORMLESS: - case CDRWIN_TRACK_TYPE_CDI: - case CDRWIN_TRACK_TYPE_MODE2_RAW: - case CDRWIN_TRACK_TYPE_CDI_RAW: return 2336; - case CDRWIN_TRACK_TYPE_CDG: - case CDRWIN_TRACK_TYPE_AUDIO: return 2352; - default: return 0; - } - } - - static TrackType CdrWinTrackTypeToTrackType(string trackType) - { - switch(trackType) - { - case CDRWIN_TRACK_TYPE_MODE1: - case CDRWIN_TRACK_TYPE_MODE1_RAW: return TrackType.CdMode1; - case CDRWIN_TRACK_TYPE_MODE2_FORM1: return TrackType.CdMode2Form1; - case CDRWIN_TRACK_TYPE_MODE2_FORM2: return TrackType.CdMode2Form2; - case CDRWIN_TRACK_TYPE_CDI_RAW: - case CDRWIN_TRACK_TYPE_CDI: - case CDRWIN_TRACK_TYPE_MODE2_RAW: - case CDRWIN_TRACK_TYPE_MODE2_FORMLESS: return TrackType.CdMode2Formless; - case CDRWIN_TRACK_TYPE_AUDIO: - case CDRWIN_TRACK_TYPE_CDG: return TrackType.Audio; - default: return TrackType.Data; - } - } - - static MediaType CdrWinIsoBusterDiscTypeToMediaType(string discType) - { - switch(discType) - { - case CDRWIN_DISK_TYPE_CD: return MediaType.CD; - case CDRWIN_DISK_TYPE_CDRW: - case CDRWIN_DISK_TYPE_CDMRW: - case CDRWIN_DISK_TYPE_CDMRW2: return MediaType.CDRW; - case CDRWIN_DISK_TYPE_DVD: return MediaType.DVDROM; - case CDRWIN_DISK_TYPE_DVDPRW: - case CDRWIN_DISK_TYPE_DVDPMRW: - case CDRWIN_DISK_TYPE_DVDPMRW2: return MediaType.DVDPRW; - case CDRWIN_DISK_TYPE_DVDPRWDL: - case CDRWIN_DISK_TYPE_DVDPMRWDL: - case CDRWIN_DISK_TYPE_DVDPMRWDL2: return MediaType.DVDPRWDL; - case CDRWIN_DISK_TYPE_DVDPR: - case CDRWIN_DISK_TYPE_DVDPVR: return MediaType.DVDPR; - case CDRWIN_DISK_TYPE_DVDPRDL: return MediaType.DVDPRDL; - case CDRWIN_DISK_TYPE_DVDRAM: return MediaType.DVDRAM; - case CDRWIN_DISK_TYPE_DVDVR: - case CDRWIN_DISK_TYPE_DVDR: return MediaType.DVDR; - case CDRWIN_DISK_TYPE_DVDRDL: return MediaType.DVDRDL; - case CDRWIN_DISK_TYPE_DVDRW: - case CDRWIN_DISK_TYPE_DVDRWDL: - case CDRWIN_DISK_TYPE_DVDRW2: return MediaType.DVDRW; - case CDRWIN_DISK_TYPE_HDDVD: return MediaType.HDDVDROM; - case CDRWIN_DISK_TYPE_HDDVDRAM: return MediaType.HDDVDRAM; - case CDRWIN_DISK_TYPE_HDDVDR: - case CDRWIN_DISK_TYPE_HDDVDRDL: return MediaType.HDDVDR; - case CDRWIN_DISK_TYPE_HDDVDRW: - case CDRWIN_DISK_TYPE_HDDVDRWDL: return MediaType.HDDVDRW; - case CDRWIN_DISK_TYPE_BD: return MediaType.BDROM; - case CDRWIN_DISK_TYPE_BDR: - case CDRWIN_DISK_TYPE_BDRDL: return MediaType.BDR; - case CDRWIN_DISK_TYPE_BDRE: - case CDRWIN_DISK_TYPE_BDREDL: return MediaType.BDRE; - default: return MediaType.Unknown; - } - } - - static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) - { - return ((byte)(sector / 75 / 60), (byte)(sector / 75 % 60), (byte)(sector % 75)); - } - - static string GetTrackMode(Track track) - { - switch(track.TrackType) - { - case TrackType.Audio when track.TrackRawBytesPerSector == 2352: return CDRWIN_TRACK_TYPE_AUDIO; - case TrackType.Data: return CDRWIN_TRACK_TYPE_MODE1; - case TrackType.CdMode1 when track.TrackRawBytesPerSector == 2352: return CDRWIN_TRACK_TYPE_MODE1_RAW; - case TrackType.CdMode2Formless - when track.TrackRawBytesPerSector != 2352: return CDRWIN_TRACK_TYPE_MODE2_FORMLESS; - case TrackType.CdMode2Form1 - when track.TrackRawBytesPerSector != 2352: return CDRWIN_TRACK_TYPE_MODE2_FORM1; - case TrackType.CdMode2Form2 - when track.TrackRawBytesPerSector != 2352: return CDRWIN_TRACK_TYPE_MODE2_FORM2; - case TrackType.CdMode2Formless when track.TrackRawBytesPerSector == 2352: - case TrackType.CdMode2Form1 when track.TrackRawBytesPerSector == 2352: - case TrackType.CdMode2Form2 - when track.TrackRawBytesPerSector == 2352: return CDRWIN_TRACK_TYPE_MODE2_RAW; - default: return CDRWIN_TRACK_TYPE_MODE1; - } - } - - static string MediaTypeToCdrwinType(MediaType type) - { - switch(type) - { - case MediaType.BDRXL: - case MediaType.BDR: return CDRWIN_DISK_TYPE_BDR; - case MediaType.BDREXL: - case MediaType.BDRE: return CDRWIN_DISK_TYPE_BDRE; - case MediaType.BDROM: - case MediaType.CBHD: - case MediaType.PS3BD: - case MediaType.PS4BD: - case MediaType.UDO: - case MediaType.UDO2: - case MediaType.UDO2_WORM: return CDRWIN_DISK_TYPE_BD; - case MediaType.CDV: - case MediaType.DDCD: - case MediaType.DDCDR: - case MediaType.DDCDRW: - case MediaType.CDPLUS: - case MediaType.CDR: - case MediaType.CDROM: - case MediaType.CDROMXA: - case MediaType.CD: - case MediaType.CDDA: - case MediaType.CDEG: - case MediaType.CDG: - case MediaType.CDI: - case MediaType.CDMIDI: - case MediaType.DTSCD: - case MediaType.JaguarCD: - case MediaType.MEGACD: - case MediaType.PD650: - case MediaType.PD650_WORM: - case MediaType.PS1CD: - case MediaType.PS2CD: - case MediaType.SuperCDROM2: - case MediaType.SVCD: - case MediaType.SVOD: - case MediaType.SATURNCD: - case MediaType.ThreeDO: - case MediaType.VCD: - case MediaType.VCDHD: return CDRWIN_DISK_TYPE_CD; - case MediaType.CDMRW: return CDRWIN_DISK_TYPE_CDMRW; - case MediaType.CDRW: return CDRWIN_DISK_TYPE_CDRW; - case MediaType.DVDPR: return CDRWIN_DISK_TYPE_DVDPR; - case MediaType.DVDPRDL: return CDRWIN_DISK_TYPE_DVDPRDL; - case MediaType.DVDPRW: return CDRWIN_DISK_TYPE_DVDPRW; - case MediaType.DVDPRWDL: return CDRWIN_DISK_TYPE_DVDPRWDL; - case MediaType.DVDR: return CDRWIN_DISK_TYPE_DVDR; - case MediaType.DVDRAM: return CDRWIN_DISK_TYPE_DVDRAM; - case MediaType.DVDRDL: return CDRWIN_DISK_TYPE_DVDRDL; - case MediaType.DVDDownload: - case MediaType.DVDROM: - case MediaType.UMD: - case MediaType.PS2DVD: - case MediaType.PS3DVD: return CDRWIN_DISK_TYPE_DVD; - case MediaType.DVDRW: return CDRWIN_DISK_TYPE_DVDRW; - case MediaType.DVDRWDL: return CDRWIN_DISK_TYPE_DVDRWDL; - case MediaType.HDDVDR: return CDRWIN_DISK_TYPE_HDDVDR; - case MediaType.HDDVDRAM: return CDRWIN_DISK_TYPE_HDDVDRAM; - case MediaType.HDDVDRDL: return CDRWIN_DISK_TYPE_HDDVDRDL; - case MediaType.HDDVDROM: return CDRWIN_DISK_TYPE_HDDVD; - case MediaType.HDDVDRW: return CDRWIN_DISK_TYPE_HDDVDRW; - case MediaType.HDDVDRWDL: return CDRWIN_DISK_TYPE_HDDVDRWDL; - default: return ""; - } - } - - struct CdrWinTrackFile - { - /// <summary>Track #</summary> - public uint Sequence; - /// <summary>Filter of file containing track</summary> - public IFilter Datafilter; - /// <summary>Offset of track start in file</summary> - public ulong Offset; - /// <summary>Type of file</summary> - public string Filetype; - } - - struct CdrWinTrack - { - /// <summary>Track #</summary> - public uint Sequence; - /// <summary>Track title (from CD-Text)</summary> - public string Title; - /// <summary>Track genre (from CD-Text)</summary> - public string Genre; - /// <summary>Track arranger (from CD-Text)</summary> - public string Arranger; - /// <summary>Track composer (from CD-Text)</summary> - public string Composer; - /// <summary>Track performer (from CD-Text)</summary> - public string Performer; - /// <summary>Track song writer (from CD-Text)</summary> - public string Songwriter; - /// <summary>Track ISRC</summary> - public string Isrc; - /// <summary>File struct for this track</summary> - public CdrWinTrackFile Trackfile; - /// <summary>Indexes on this track</summary> - public Dictionary<int, ulong> Indexes; - /// <summary>Track pre-gap in sectors</summary> - public ulong Pregap; - /// <summary>Track post-gap in sectors</summary> - public ulong Postgap; - /// <summary>Digical Copy Permitted</summary> - public bool FlagDcp; - /// <summary>Track is quadraphonic</summary> - public bool Flag4ch; - /// <summary>Track has preemphasis</summary> - public bool FlagPre; - /// <summary>Track has SCMS</summary> - public bool FlagScms; - /// <summary>Bytes per sector</summary> - public ushort Bps; - /// <summary>Sectors in track</summary> - public ulong Sectors; - /// <summary>Track type</summary> - public string Tracktype; - /// <summary>Track session</summary> - public ushort Session; - } - - struct CdrWinDisc - { - /// <summary>Disk title (from CD-Text)</summary> - public string Title; - /// <summary>Disk genre (from CD-Text)</summary> - public string Genre; - /// <summary>Disk arranger (from CD-Text)</summary> - public string Arranger; - /// <summary>Disk composer (from CD-Text)</summary> - public string Composer; - /// <summary>Disk performer (from CD-Text)</summary> - public string Performer; - /// <summary>Disk song writer (from CD-Text)</summary> - public string Songwriter; - /// <summary>Media catalog number</summary> - public string Mcn; - /// <summary>Disk type</summary> - public MediaType Disktype; - /// <summary>Disk type string</summary> - public string Disktypestr; - /// <summary>Disk CDDB ID</summary> - public string DiskId; - /// <summary>Disk UPC/EAN</summary> - public string Barcode; - /// <summary>Sessions</summary> - public List<Session> Sessions; - /// <summary>Tracks</summary> - public List<CdrWinTrack> Tracks; - /// <summary>Disk comment</summary> - public string Comment; - /// <summary>File containing CD-Text</summary> - public string Cdtextfile; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/Structs.cs b/DiscImageChef.DiscImages/CDRWin/Structs.cs new file mode 100644 index 000000000..761ca6883 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/Structs.cs @@ -0,0 +1,132 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class CdrWin + { + struct CdrWinTrackFile + { + /// <summary>Track #</summary> + public uint Sequence; + /// <summary>Filter of file containing track</summary> + public IFilter Datafilter; + /// <summary>Offset of track start in file</summary> + public ulong Offset; + /// <summary>Type of file</summary> + public string Filetype; + } + + struct CdrWinTrack + { + /// <summary>Track #</summary> + public uint Sequence; + /// <summary>Track title (from CD-Text)</summary> + public string Title; + /// <summary>Track genre (from CD-Text)</summary> + public string Genre; + /// <summary>Track arranger (from CD-Text)</summary> + public string Arranger; + /// <summary>Track composer (from CD-Text)</summary> + public string Composer; + /// <summary>Track performer (from CD-Text)</summary> + public string Performer; + /// <summary>Track song writer (from CD-Text)</summary> + public string Songwriter; + /// <summary>Track ISRC</summary> + public string Isrc; + /// <summary>File struct for this track</summary> + public CdrWinTrackFile Trackfile; + /// <summary>Indexes on this track</summary> + public Dictionary<int, ulong> Indexes; + /// <summary>Track pre-gap in sectors</summary> + public ulong Pregap; + /// <summary>Track post-gap in sectors</summary> + public ulong Postgap; + /// <summary>Digical Copy Permitted</summary> + public bool FlagDcp; + /// <summary>Track is quadraphonic</summary> + public bool Flag4ch; + /// <summary>Track has preemphasis</summary> + public bool FlagPre; + /// <summary>Track has SCMS</summary> + public bool FlagScms; + /// <summary>Bytes per sector</summary> + public ushort Bps; + /// <summary>Sectors in track</summary> + public ulong Sectors; + /// <summary>Track type</summary> + public string Tracktype; + /// <summary>Track session</summary> + public ushort Session; + } + + struct CdrWinDisc + { + /// <summary>Disk title (from CD-Text)</summary> + public string Title; + /// <summary>Disk genre (from CD-Text)</summary> + public string Genre; + /// <summary>Disk arranger (from CD-Text)</summary> + public string Arranger; + /// <summary>Disk composer (from CD-Text)</summary> + public string Composer; + /// <summary>Disk performer (from CD-Text)</summary> + public string Performer; + /// <summary>Disk song writer (from CD-Text)</summary> + public string Songwriter; + /// <summary>Media catalog number</summary> + public string Mcn; + /// <summary>Disk type</summary> + public MediaType Disktype; + /// <summary>Disk type string</summary> + public string Disktypestr; + /// <summary>Disk CDDB ID</summary> + public string DiskId; + /// <summary>Disk UPC/EAN</summary> + public string Barcode; + /// <summary>Sessions</summary> + public List<Session> Sessions; + /// <summary>Tracks</summary> + public List<CdrWinTrack> Tracks; + /// <summary>Disk comment</summary> + public string Comment; + /// <summary>File containing CD-Text</summary> + public string Cdtextfile; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/Unsupported.cs b/DiscImageChef.DiscImages/CDRWin/Unsupported.cs new file mode 100644 index 000000000..731db05d6 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/Unsupported.cs @@ -0,0 +1,42 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class CdrWin + { + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CDRWin/Write.cs b/DiscImageChef.DiscImages/CDRWin/Write.cs new file mode 100644 index 000000000..48dc0e9d6 --- /dev/null +++ b/DiscImageChef.DiscImages/CDRWin/Write.cs @@ -0,0 +1,508 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes CDRWin cuesheets (cue/bin). +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class CdrWin + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(options != null) + { + if(options.TryGetValue("separate", out string tmpValue)) + { + if(!bool.TryParse(tmpValue, out separateTracksWriting)) + { + ErrorMessage = "Invalid value for split option"; + return false; + } + + if(separateTracksWriting) + { + ErrorMessage = "Separate tracksnot yet implemented"; + return false; + } + } + } + else separateTracksWriting = false; + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + // TODO: Separate tracks + try + { + writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); + descriptorStream = new StreamWriter(path, false, Encoding.ASCII); + } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + discimage = new CdrWinDisc + { + Disktype = mediaType, + Sessions = new List<Session>(), + Tracks = new List<CdrWinTrack>() + }; + + trackFlags = new Dictionary<byte, byte>(); + trackIsrcs = new Dictionary<byte, string>(); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + switch(tag) + { + case MediaTagType.CD_MCN: + discimage.Mcn = Encoding.ASCII.GetString(data); + return true; + case MediaTagType.CD_TEXT: + FileStream cdTextStream = new FileStream(writingBaseName + "_cdtext.bin", FileMode.Create, + FileAccess.ReadWrite, FileShare.None); + cdTextStream.Write(data, 0, data.Length); + discimage.Cdtextfile = Path.GetFileName(cdTextStream.Name); + cdTextStream.Close(); + return true; + default: + ErrorMessage = $"Unsupported media tag {tag}"; + return false; + } + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) + { + ErrorMessage = "Invalid write mode for this sector"; + return false; + } + + if(data.Length != track.TrackRawBytesPerSector) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(track.TrackBytesPerSector != track.TrackRawBytesPerSector) + { + ErrorMessage = "Invalid write mode for this sector"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + if(data.Length % track.TrackRawBytesPerSector != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(data.Length != track.TrackRawBytesPerSector) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + return true; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + FileStream trackStream = writingStreams.FirstOrDefault(kvp => kvp.Key == track.TrackSequence).Value; + + if(trackStream == null) + { + ErrorMessage = $"Can't found file containing {sectorAddress}"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + if(data.Length % track.TrackRawBytesPerSector != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + trackStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + trackStream.Write(data, 0, data.Length); + + return true; + } + + public bool SetTracks(List<Track> tracks) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(tracks == null || tracks.Count == 0) + { + ErrorMessage = "Invalid tracks sent"; + return false; + } + + if(writingTracks != null && writingStreams != null) + foreach(FileStream oldTrack in writingStreams.Select(t => t.Value).Distinct()) + oldTrack.Close(); + + ulong currentOffset = 0; + writingTracks = new List<Track>(); + foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) + { + Track newTrack = track; + newTrack.TrackFile = separateTracksWriting + ? writingBaseName + $"_track{track.TrackSequence:D2}.bin" + : writingBaseName + ".bin"; + newTrack.TrackFileOffset = separateTracksWriting ? 0 : currentOffset; + writingTracks.Add(newTrack); + currentOffset += (ulong)newTrack.TrackRawBytesPerSector * + (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); + } + + writingStreams = new Dictionary<uint, FileStream>(); + if(separateTracksWriting) + foreach(Track track in writingTracks) + writingStreams.Add(track.TrackSequence, + new FileStream(track.TrackFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, + FileShare.None)); + else + { + FileStream jointstream = new FileStream(writingBaseName + ".bin", FileMode.OpenOrCreate, + FileAccess.ReadWrite, FileShare.None); + foreach(Track track in writingTracks) writingStreams.Add(track.TrackSequence, jointstream); + } + + return true; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(separateTracksWriting) + foreach(FileStream writingStream in writingStreams.Values) + { + writingStream.Flush(); + writingStream.Close(); + } + else + { + writingStreams.First().Value.Flush(); + writingStreams.First().Value.Close(); + } + + int currentSession = 0; + + if(!string.IsNullOrWhiteSpace(discimage.Comment)) + { + string[] commentLines = discimage.Comment.Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries); + foreach(string line in commentLines) descriptorStream.WriteLine("REM {0}", line); + } + + descriptorStream.WriteLine("REM ORIGINAL MEDIA-TYPE {0}", MediaTypeToCdrwinType(imageInfo.MediaType)); + + if(!string.IsNullOrEmpty(discimage.Cdtextfile)) + descriptorStream.WriteLine("CDTEXTFILE \"{0}\"", Path.GetFileName(discimage.Cdtextfile)); + + if(!string.IsNullOrEmpty(discimage.Title)) descriptorStream.WriteLine("TITLE {0}", discimage.Title); + + if(!string.IsNullOrEmpty(discimage.Mcn)) descriptorStream.WriteLine("CATALOG {0}", discimage.Mcn); + + if(!string.IsNullOrEmpty(discimage.Barcode)) descriptorStream.WriteLine("UPC_EAN {0}", discimage.Barcode); + + if(!separateTracksWriting) + descriptorStream.WriteLine("FILE \"{0}\" BINARY", Path.GetFileName(writingStreams.First().Value.Name)); + + foreach(Track track in writingTracks) + { + if(track.TrackSession > currentSession) descriptorStream.WriteLine("REM SESSION {0}", ++currentSession); + + if(separateTracksWriting) + descriptorStream.WriteLine("FILE \"{0}\" BINARY", Path.GetFileName(track.TrackFile)); + + (byte minute, byte second, byte frame) msf = LbaToMsf(track.TrackStartSector); + descriptorStream.WriteLine(" TRACK {0:D2} {1}", track.TrackSequence, GetTrackMode(track)); + + if(trackFlags.TryGetValue((byte)track.TrackSequence, out byte flagsByte)) + if(flagsByte != 0 && flagsByte != (byte)CdFlags.DataTrack) + { + CdFlags flags = (CdFlags)flagsByte; + descriptorStream.WriteLine(" FLAGS{0}{1}{2}", + flags.HasFlag(CdFlags.CopyPermitted) ? " DCP" : "", + flags.HasFlag(CdFlags.FourChannel) ? " 4CH" : "", + flags.HasFlag(CdFlags.PreEmphasis) ? " PRE" : ""); + } + + if(trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc)) + descriptorStream.WriteLine(" ISRC {0}", isrc); + + descriptorStream.WriteLine(" INDEX {0:D2} {1:D2}:{2:D2}:{3:D2}", 1, msf.minute, msf.second, + msf.frame); + } + + descriptorStream.Flush(); + descriptorStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + writingTracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + { + if(data.Length != 1) + { + ErrorMessage = "Incorrect data size for track flags"; + return false; + } + + trackFlags.Add((byte)track.TrackSequence, data[0]); + return true; + } + case SectorTagType.CdTrackIsrc: + { + if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); + return true; + } + default: + ErrorMessage = $"Unsupported tag type {tag}"; + return false; + } + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + return WriteSectorTag(data, sectorAddress, tag); + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + + public bool SetMetadata(ImageInfo metadata) + { + discimage.Barcode = metadata.MediaBarcode; + discimage.Comment = metadata.Comments; + discimage.Title = metadata.MediaTitle; + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD/CHD.cs b/DiscImageChef.DiscImages/CHD/CHD.cs new file mode 100644 index 000000000..7af651d51 --- /dev/null +++ b/DiscImageChef.DiscImages/CHD/CHD.cs @@ -0,0 +1,100 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CHD.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages MAME Compressed Hunks of Data disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Implement PCMCIA support + public partial class Chd : IMediaImage + { + /// <summary>"MComprHD"</summary> + readonly byte[] chdTag = {0x4D, 0x43, 0x6F, 0x6D, 0x70, 0x72, 0x48, 0x44}; + uint bytesPerHunk; + byte[] cis; + byte[] expectedChecksum; + uint hdrCompression; + uint hdrCompression1; + uint hdrCompression2; + uint hdrCompression3; + Dictionary<ulong, byte[]> hunkCache; + byte[] hunkMap; + ulong[] hunkTable; + uint[] hunkTableSmall; + byte[] identify; + ImageInfo imageInfo; + Stream imageStream; + bool isCdrom; + bool isGdrom; + bool isHdd; + uint mapVersion; + int maxBlockCache; + int maxSectorCache; + Dictionary<ulong, uint> offsetmap; + List<Partition> partitions; + Dictionary<ulong, byte[]> sectorCache; + uint sectorsPerHunk; + bool swapAudio; + uint totalHunks; + Dictionary<uint, Track> tracks; + + public Chd() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Application = "MAME", + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD/Constants.cs b/DiscImageChef.DiscImages/CHD/Constants.cs new file mode 100644 index 000000000..bca537801 --- /dev/null +++ b/DiscImageChef.DiscImages/CHD/Constants.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for MAME Compressed Hunks of Data disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Chd + { + /// <summary>"GDDD"</summary> + const uint HARD_DISK_METADATA = 0x47444444; + /// <summary>"IDNT"</summary> + const uint HARD_DISK_IDENT_METADATA = 0x49444E54; + /// <summary>"KEY "</summary> + const uint HARD_DISK_KEY_METADATA = 0x4B455920; + /// <summary>"CIS "</summary> + const uint PCMCIA_CIS_METADATA = 0x43495320; + /// <summary>"CHCD"</summary> + const uint CDROM_OLD_METADATA = 0x43484344; + /// <summary>"CHTR"</summary> + const uint CDROM_TRACK_METADATA = 0x43485452; + /// <summary>"CHT2"</summary> + const uint CDROM_TRACK_METADATA2 = 0x43485432; + /// <summary>"CHGT"</summary> + const uint GDROM_OLD_METADATA = 0x43484754; + /// <summary>"CHGD"</summary> + const uint GDROM_METADATA = 0x43484744; + /// <summary>"AVAV"</summary> + const uint AV_METADATA = 0x41564156; + /// <summary>"AVLD"</summary> + const uint AV_LASER_DISC_METADATA = 0x41564C44; + + const string REGEX_METADATA_HDD = + @"CYLS:(?<cylinders>\d+),HEADS:(?<heads>\d+),SECS:(?<sectors>\d+),BPS:(?<bps>\d+)"; + const string REGEX_METADATA_CDROM = + @"TRACK:(?<track>\d+) TYPE:(?<track_type>\S+) SUBTYPE:(?<sub_type>\S+) FRAMES:(?<frames>\d+)"; + const string REGEX_METADATA_CDROM2 = + @"TRACK:(?<track>\d+) TYPE:(?<track_type>\S+) SUBTYPE:(?<sub_type>\S+) FRAMES:(?<frames>\d+) PREGAP:(?<pregap>\d+) PGTYPE:(?<pgtype>\S+) PGSUB:(?<pgsub>\S+) POSTGAP:(?<postgap>\d+)"; + const string REGEX_METADATA_GDROM = + @"TRACK:(?<track>\d+) TYPE:(?<track_type>\S+) SUBTYPE:(?<sub_type>\S+) FRAMES:(?<frames>\d+) PAD:(?<pad>\d+) PREGAP:(?<pregap>\d+) PGTYPE:(?<pgtype>\S+) PGSUB:(?<pgsub>\S+) POSTGAP:(?<postgap>\d+)"; + + const string TRACK_TYPE_MODE1 = "MODE1"; + const string TRACK_TYPE_MODE1_2K = "MODE1/2048"; + const string TRACK_TYPE_MODE1_RAW = "MODE1_RAW"; + const string TRACK_TYPE_MODE1_RAW_2K = "MODE1/2352"; + const string TRACK_TYPE_MODE2 = "MODE2"; + const string TRACK_TYPE_MODE2_2K = "MODE2/2336"; + const string TRACK_TYPE_MODE2_F1 = "MODE2_FORM1"; + const string TRACK_TYPE_MODE2_F1_2K = "MODE2/2048"; + const string TRACK_TYPE_MODE2_F2 = "MODE2_FORM2"; + const string TRACK_TYPE_MODE2_F2_2K = "MODE2/2324"; + const string TRACK_TYPE_MODE2_FM = "MODE2_FORM_MIX"; + const string TRACK_TYPE_MODE2_RAW = "MODE2_RAW"; + const string TRACK_TYPE_MODE2_RAW_2K = "MODE2/2352"; + const string TRACK_TYPE_AUDIO = "AUDIO"; + + const string SUB_TYPE_COOKED = "RW"; + const string SUB_TYPE_RAW = "RW_RAW"; + const string SUB_TYPE_NONE = "NONE"; + + const int MAX_CACHE_SIZE = 16777216; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD/Enums.cs b/DiscImageChef.DiscImages/CHD/Enums.cs new file mode 100644 index 000000000..db6049c89 --- /dev/null +++ b/DiscImageChef.DiscImages/CHD/Enums.cs @@ -0,0 +1,88 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for MAME Compressed Hunks of Data disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Chd + { + enum ChdCompression : uint + { + None = 0, + Zlib = 1, + ZlibPlus = 2, + Av = 3 + } + + enum ChdFlags : uint + { + HasParent = 1, + Writable = 2 + } + + enum Chdv3EntryFlags : byte + { + /// <summary>Invalid</summary> + Invalid = 0, + /// <summary>Compressed with primary codec</summary> + Compressed = 1, + /// <summary>Uncompressed</summary> + Uncompressed = 2, + /// <summary>Use offset as data</summary> + Mini = 3, + /// <summary>Same as another hunk in file</summary> + SelfHunk = 4, + /// <summary>Same as another hunk in parent</summary> + ParentHunk = 5, + /// <summary>Compressed with secondary codec (FLAC)</summary> + SecondCompressed = 6 + } + + enum ChdOldTrackType : uint + { + Mode1 = 0, + Mode1Raw, + Mode2, + Mode2Form1, + Mode2Form2, + Mode2FormMix, + Mode2Raw, + Audio + } + + enum ChdOldSubType : uint + { + Cooked = 0, + Raw, + None + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD/Helpers.cs b/DiscImageChef.DiscImages/CHD/Helpers.cs new file mode 100644 index 000000000..6aca055c6 --- /dev/null +++ b/DiscImageChef.DiscImages/CHD/Helpers.cs @@ -0,0 +1,176 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for MAME Compressed Hunks of Data disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using SharpCompress.Compressors; +using SharpCompress.Compressors.Deflate; + +namespace DiscImageChef.DiscImages +{ + public partial class Chd + { + Track GetTrack(ulong sector) + { + Track track = new Track(); + foreach(KeyValuePair<ulong, uint> kvp in offsetmap.Where(kvp => sector >= kvp.Key)) + tracks.TryGetValue(kvp.Value, out track); + + return track; + } + + ulong GetAbsoluteSector(ulong relativeSector, uint track) + { + tracks.TryGetValue(track, out Track dicTrack); + return dicTrack.TrackStartSector + relativeSector; + } + + byte[] GetHunk(ulong hunkNo) + { + if(hunkCache.TryGetValue(hunkNo, out byte[] hunk)) return hunk; + + switch(mapVersion) + { + case 1: + ulong offset = hunkTable[hunkNo] & 0x00000FFFFFFFFFFF; + ulong length = hunkTable[hunkNo] >> 44; + + byte[] compHunk = new byte[length]; + imageStream.Seek((long)offset, SeekOrigin.Begin); + imageStream.Read(compHunk, 0, compHunk.Length); + + if(length == sectorsPerHunk * imageInfo.SectorSize) hunk = compHunk; + else if((ChdCompression)hdrCompression > ChdCompression.Zlib) + throw new + ImageNotSupportedException($"Unsupported compression {(ChdCompression)hdrCompression}"); + else + { + DeflateStream zStream = + new DeflateStream(new MemoryStream(compHunk), CompressionMode.Decompress); + hunk = new byte[sectorsPerHunk * imageInfo.SectorSize]; + int read = zStream.Read(hunk, 0, (int)(sectorsPerHunk * imageInfo.SectorSize)); + if(read != sectorsPerHunk * imageInfo.SectorSize) + throw new + IOException($"Unable to decompress hunk correctly, got {read} bytes, expected {sectorsPerHunk * imageInfo.SectorSize}"); + + zStream.Close(); + } + + break; + case 3: + byte[] entryBytes = new byte[16]; + Array.Copy(hunkMap, (int)(hunkNo * 16), entryBytes, 0, 16); + ChdMapV3Entry entry = BigEndianMarshal.ByteArrayToStructureBigEndian<ChdMapV3Entry>(entryBytes); + switch((Chdv3EntryFlags)(entry.flags & 0x0F)) + { + case Chdv3EntryFlags.Invalid: throw new ArgumentException("Invalid hunk found."); + case Chdv3EntryFlags.Compressed: + switch((ChdCompression)hdrCompression) + { + case ChdCompression.None: goto uncompressedV3; + case ChdCompression.Zlib: + case ChdCompression.ZlibPlus: + if(isHdd) + { + byte[] zHunk = new byte[(entry.lengthLsb << 16) + entry.lengthLsb]; + imageStream.Seek((long)entry.offset, SeekOrigin.Begin); + imageStream.Read(zHunk, 0, zHunk.Length); + DeflateStream zStream = + new DeflateStream(new MemoryStream(zHunk), CompressionMode.Decompress); + hunk = new byte[bytesPerHunk]; + int read = zStream.Read(hunk, 0, (int)bytesPerHunk); + if(read != bytesPerHunk) + throw new + IOException($"Unable to decompress hunk correctly, got {read} bytes, expected {bytesPerHunk}"); + + zStream.Close(); + } + // TODO: Guess wth is MAME doing with these hunks + else + throw new + ImageNotSupportedException("Compressed CD/GD-ROM hunks are not yet supported"); + + break; + case ChdCompression.Av: + throw new + ImageNotSupportedException($"Unsupported compression {(ChdCompression)hdrCompression}"); + } + + break; + case Chdv3EntryFlags.Uncompressed: + uncompressedV3: + hunk = new byte[bytesPerHunk]; + imageStream.Seek((long)entry.offset, SeekOrigin.Begin); + imageStream.Read(hunk, 0, hunk.Length); + break; + case Chdv3EntryFlags.Mini: + hunk = new byte[bytesPerHunk]; + byte[] mini; + mini = BigEndianBitConverter.GetBytes(entry.offset); + for(int i = 0; i < bytesPerHunk; i++) hunk[i] = mini[i % 8]; + + break; + case Chdv3EntryFlags.SelfHunk: return GetHunk(entry.offset); + case Chdv3EntryFlags.ParentHunk: + throw new ImageNotSupportedException("Parent images are not supported"); + case Chdv3EntryFlags.SecondCompressed: + throw new ImageNotSupportedException("FLAC is not supported"); + default: + throw new ImageNotSupportedException($"Hunk type {entry.flags & 0xF} is not supported"); + } + + break; + case 5: + if(hdrCompression == 0) + { + hunk = new byte[bytesPerHunk]; + imageStream.Seek(hunkTableSmall[hunkNo] * bytesPerHunk, SeekOrigin.Begin); + imageStream.Read(hunk, 0, hunk.Length); + } + else throw new ImageNotSupportedException("Compressed v5 hunks not yet supported"); + + break; + default: throw new ImageNotSupportedException($"Unsupported hunk map version {mapVersion}"); + } + + if(hunkCache.Count >= maxBlockCache) hunkCache.Clear(); + + hunkCache.Add(hunkNo, hunk); + + return hunk; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD/Identify.cs b/DiscImageChef.DiscImages/CHD/Identify.cs new file mode 100644 index 000000000..04721c79b --- /dev/null +++ b/DiscImageChef.DiscImages/CHD/Identify.cs @@ -0,0 +1,51 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies MAME Compressed Hunks of Data disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Chd + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + byte[] magic = new byte[8]; + stream.Read(magic, 0, 8); + + return chdTag.SequenceEqual(magic); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD/Properties.cs b/DiscImageChef.DiscImages/CHD/Properties.cs new file mode 100644 index 000000000..c332384a6 --- /dev/null +++ b/DiscImageChef.DiscImages/CHD/Properties.cs @@ -0,0 +1,85 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for MAME Compressed Hunks of Data disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Chd + { + public ImageInfo Info => imageInfo; + public string Name => "MAME Compressed Hunks of Data"; + public Guid Id => new Guid("0D50233A-08BD-47D4-988B-27EAA0358597"); + public string Format => "Compressed Hunks of Data"; + public List<Partition> Partitions + { + get + { + if(isHdd) + throw new + FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return partitions; + } + } + public List<Track> Tracks + { + get + { + if(isHdd) + throw new + FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return tracks.Values.ToList(); + } + } + public List<Session> Sessions + { + get + { + if(isHdd) + throw new + FeaturedNotSupportedByDiscImageException("Cannot access optical sessions on a hard disk image"); + + throw new NotImplementedException(); + } + } + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD.cs b/DiscImageChef.DiscImages/CHD/Read.cs similarity index 74% rename from DiscImageChef.DiscImages/CHD.cs rename to DiscImageChef.DiscImages/CHD/Read.cs index 46612fce9..1465a82db 100644 --- a/DiscImageChef.DiscImages/CHD.cs +++ b/DiscImageChef.DiscImages/CHD/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : CHD.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages MAME Compressed Hunks of Data disk images. +// Reads MAME Compressed Hunks of Data disk images. // // --[ License ] -------------------------------------------------------------- // @@ -44,178 +44,11 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Decoders.ATA; -using Schemas; -using SharpCompress.Compressors; -using SharpCompress.Compressors.Deflate; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // TODO: Implement PCMCIA support - public class Chd : IMediaImage + public partial class Chd { - /// <summary>"GDDD"</summary> - const uint HARD_DISK_METADATA = 0x47444444; - /// <summary>"IDNT"</summary> - const uint HARD_DISK_IDENT_METADATA = 0x49444E54; - /// <summary>"KEY "</summary> - const uint HARD_DISK_KEY_METADATA = 0x4B455920; - /// <summary>"CIS "</summary> - const uint PCMCIA_CIS_METADATA = 0x43495320; - /// <summary>"CHCD"</summary> - const uint CDROM_OLD_METADATA = 0x43484344; - /// <summary>"CHTR"</summary> - const uint CDROM_TRACK_METADATA = 0x43485452; - /// <summary>"CHT2"</summary> - const uint CDROM_TRACK_METADATA2 = 0x43485432; - /// <summary>"CHGT"</summary> - const uint GDROM_OLD_METADATA = 0x43484754; - /// <summary>"CHGD"</summary> - const uint GDROM_METADATA = 0x43484744; - /// <summary>"AVAV"</summary> - const uint AV_METADATA = 0x41564156; - /// <summary>"AVLD"</summary> - const uint AV_LASER_DISC_METADATA = 0x41564C44; - - const string REGEX_METADATA_HDD = - @"CYLS:(?<cylinders>\d+),HEADS:(?<heads>\d+),SECS:(?<sectors>\d+),BPS:(?<bps>\d+)"; - const string REGEX_METADATA_CDROM = - @"TRACK:(?<track>\d+) TYPE:(?<track_type>\S+) SUBTYPE:(?<sub_type>\S+) FRAMES:(?<frames>\d+)"; - const string REGEX_METADATA_CDROM2 = - @"TRACK:(?<track>\d+) TYPE:(?<track_type>\S+) SUBTYPE:(?<sub_type>\S+) FRAMES:(?<frames>\d+) PREGAP:(?<pregap>\d+) PGTYPE:(?<pgtype>\S+) PGSUB:(?<pgsub>\S+) POSTGAP:(?<postgap>\d+)"; - const string REGEX_METADATA_GDROM = - @"TRACK:(?<track>\d+) TYPE:(?<track_type>\S+) SUBTYPE:(?<sub_type>\S+) FRAMES:(?<frames>\d+) PAD:(?<pad>\d+) PREGAP:(?<pregap>\d+) PGTYPE:(?<pgtype>\S+) PGSUB:(?<pgsub>\S+) POSTGAP:(?<postgap>\d+)"; - - const string TRACK_TYPE_MODE1 = "MODE1"; - const string TRACK_TYPE_MODE1_2K = "MODE1/2048"; - const string TRACK_TYPE_MODE1_RAW = "MODE1_RAW"; - const string TRACK_TYPE_MODE1_RAW_2K = "MODE1/2352"; - const string TRACK_TYPE_MODE2 = "MODE2"; - const string TRACK_TYPE_MODE2_2K = "MODE2/2336"; - const string TRACK_TYPE_MODE2_F1 = "MODE2_FORM1"; - const string TRACK_TYPE_MODE2_F1_2K = "MODE2/2048"; - const string TRACK_TYPE_MODE2_F2 = "MODE2_FORM2"; - const string TRACK_TYPE_MODE2_F2_2K = "MODE2/2324"; - const string TRACK_TYPE_MODE2_FM = "MODE2_FORM_MIX"; - const string TRACK_TYPE_MODE2_RAW = "MODE2_RAW"; - const string TRACK_TYPE_MODE2_RAW_2K = "MODE2/2352"; - const string TRACK_TYPE_AUDIO = "AUDIO"; - - const string SUB_TYPE_COOKED = "RW"; - const string SUB_TYPE_RAW = "RW_RAW"; - const string SUB_TYPE_NONE = "NONE"; - - const int MAX_CACHE_SIZE = 16777216; - - /// <summary>"MComprHD"</summary> - readonly byte[] chdTag = {0x4D, 0x43, 0x6F, 0x6D, 0x70, 0x72, 0x48, 0x44}; - uint bytesPerHunk; - byte[] cis; - byte[] expectedChecksum; - uint hdrCompression; - uint hdrCompression1; - uint hdrCompression2; - uint hdrCompression3; - Dictionary<ulong, byte[]> hunkCache; - byte[] hunkMap; - ulong[] hunkTable; - uint[] hunkTableSmall; - byte[] identify; - ImageInfo imageInfo; - Stream imageStream; - bool isCdrom; - bool isGdrom; - bool isHdd; - uint mapVersion; - int maxBlockCache; - int maxSectorCache; - Dictionary<ulong, uint> offsetmap; - List<Partition> partitions; - Dictionary<ulong, byte[]> sectorCache; - uint sectorsPerHunk; - bool swapAudio; - uint totalHunks; - Dictionary<uint, Track> tracks; - - public Chd() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Application = "MAME", - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "MAME Compressed Hunks of Data"; - public Guid Id => new Guid("0D50233A-08BD-47D4-988B-27EAA0358597"); - - public string Format => "Compressed Hunks of Data"; - - public List<Partition> Partitions - { - get - { - if(isHdd) - throw new - FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); - - return partitions; - } - } - - public List<Track> Tracks - { - get - { - if(isHdd) - throw new - FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); - - return tracks.Values.ToList(); - } - } - - public List<Session> Sessions - { - get - { - if(isHdd) - throw new - FeaturedNotSupportedByDiscImageException("Cannot access optical sessions on a hard disk image"); - - throw new NotImplementedException(); - } - } - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - byte[] magic = new byte[8]; - stream.Read(magic, 0, 8); - - return chdTag.SequenceEqual(magic); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -290,7 +123,7 @@ namespace DiscImageChef.DiscImages } DateTime end = DateTime.UtcNow; - DicConsole.DebugWriteLine("CHD plugin","Took {0} seconds", (end - start).TotalSeconds); + DicConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); imageInfo.MediaType = MediaType.GENERIC_HDD; imageInfo.Sectors = hdrV1.hunksize * hdrV1.totalhunks; @@ -363,7 +196,7 @@ namespace DiscImageChef.DiscImages } DateTime end = DateTime.UtcNow; - DicConsole.DebugWriteLine("CHD plugin","Took {0} seconds", (end - start).TotalSeconds); + DicConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); imageInfo.MediaType = MediaType.GENERIC_HDD; imageInfo.Sectors = hdrV2.hunksize * hdrV2.totalhunks; @@ -419,7 +252,7 @@ namespace DiscImageChef.DiscImages stream.Read(hunkMap, 0, hunkMap.Length); DateTime end = DateTime.UtcNow; - DicConsole.DebugWriteLine("CHD plugin","Took {0} seconds", (end - start).TotalSeconds); + DicConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); nextMetaOff = hdrV3.metaoffset; @@ -464,7 +297,7 @@ namespace DiscImageChef.DiscImages stream.Read(hunkMap, 0, hunkMap.Length); DateTime end = DateTime.UtcNow; - DicConsole.DebugWriteLine("CHD plugin","Took {0} seconds", (end - start).TotalSeconds); + DicConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); nextMetaOff = hdrV4.metaoffset; @@ -546,7 +379,7 @@ namespace DiscImageChef.DiscImages } DateTime end = DateTime.UtcNow; - DicConsole.DebugWriteLine("CHD plugin","Took {0} seconds", (end - start).TotalSeconds); + DicConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); } else throw new ImageNotSupportedException("Cannot read compressed CHD version 5"); @@ -1068,7 +901,7 @@ namespace DiscImageChef.DiscImages break; // "IDNT" case HARD_DISK_IDENT_METADATA: - Identify.IdentifyDevice? idnt = Decoders.ATA.Identify.Decode(meta); + Decoders.ATA.Identify.IdentifyDevice? idnt = Decoders.ATA.Identify.Decode(meta); if(idnt.HasValue) { imageInfo.MediaManufacturer = idnt.Value.MediaManufacturer; @@ -1336,9 +1169,6 @@ namespace DiscImageChef.DiscImages return expectedChecksum.SequenceEqual(calculated); } - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - public byte[] ReadSector(ulong sectorAddress) { if(sectorAddress > imageInfo.Sectors - 1) @@ -1836,590 +1666,5 @@ namespace DiscImageChef.DiscImages return ReadSectorLong(GetAbsoluteSector(sectorAddress, track), length); } - - Track GetTrack(ulong sector) - { - Track track = new Track(); - foreach(KeyValuePair<ulong, uint> kvp in offsetmap.Where(kvp => sector >= kvp.Key)) - tracks.TryGetValue(kvp.Value, out track); - - return track; - } - - ulong GetAbsoluteSector(ulong relativeSector, uint track) - { - tracks.TryGetValue(track, out Track dicTrack); - return dicTrack.TrackStartSector + relativeSector; - } - - byte[] GetHunk(ulong hunkNo) - { - if(hunkCache.TryGetValue(hunkNo, out byte[] hunk)) return hunk; - - switch(mapVersion) - { - case 1: - ulong offset = hunkTable[hunkNo] & 0x00000FFFFFFFFFFF; - ulong length = hunkTable[hunkNo] >> 44; - - byte[] compHunk = new byte[length]; - imageStream.Seek((long)offset, SeekOrigin.Begin); - imageStream.Read(compHunk, 0, compHunk.Length); - - if(length == sectorsPerHunk * imageInfo.SectorSize) hunk = compHunk; - else if((ChdCompression)hdrCompression > ChdCompression.Zlib) - throw new - ImageNotSupportedException($"Unsupported compression {(ChdCompression)hdrCompression}"); - else - { - DeflateStream zStream = - new DeflateStream(new MemoryStream(compHunk), CompressionMode.Decompress); - hunk = new byte[sectorsPerHunk * imageInfo.SectorSize]; - int read = zStream.Read(hunk, 0, (int)(sectorsPerHunk * imageInfo.SectorSize)); - if(read != sectorsPerHunk * imageInfo.SectorSize) - throw new - IOException($"Unable to decompress hunk correctly, got {read} bytes, expected {sectorsPerHunk * imageInfo.SectorSize}"); - - zStream.Close(); - } - - break; - case 3: - byte[] entryBytes = new byte[16]; - Array.Copy(hunkMap, (int)(hunkNo * 16), entryBytes, 0, 16); - ChdMapV3Entry entry = BigEndianMarshal.ByteArrayToStructureBigEndian<ChdMapV3Entry>(entryBytes); - switch((Chdv3EntryFlags)(entry.flags & 0x0F)) - { - case Chdv3EntryFlags.Invalid: throw new ArgumentException("Invalid hunk found."); - case Chdv3EntryFlags.Compressed: - switch((ChdCompression)hdrCompression) - { - case ChdCompression.None: goto uncompressedV3; - case ChdCompression.Zlib: - case ChdCompression.ZlibPlus: - if(isHdd) - { - byte[] zHunk = new byte[(entry.lengthLsb << 16) + entry.lengthLsb]; - imageStream.Seek((long)entry.offset, SeekOrigin.Begin); - imageStream.Read(zHunk, 0, zHunk.Length); - DeflateStream zStream = - new DeflateStream(new MemoryStream(zHunk), CompressionMode.Decompress); - hunk = new byte[bytesPerHunk]; - int read = zStream.Read(hunk, 0, (int)bytesPerHunk); - if(read != bytesPerHunk) - throw new - IOException($"Unable to decompress hunk correctly, got {read} bytes, expected {bytesPerHunk}"); - - zStream.Close(); - } - // TODO: Guess wth is MAME doing with these hunks - else - throw new - ImageNotSupportedException("Compressed CD/GD-ROM hunks are not yet supported"); - - break; - case ChdCompression.Av: - throw new - ImageNotSupportedException($"Unsupported compression {(ChdCompression)hdrCompression}"); - } - - break; - case Chdv3EntryFlags.Uncompressed: - uncompressedV3: - hunk = new byte[bytesPerHunk]; - imageStream.Seek((long)entry.offset, SeekOrigin.Begin); - imageStream.Read(hunk, 0, hunk.Length); - break; - case Chdv3EntryFlags.Mini: - hunk = new byte[bytesPerHunk]; - byte[] mini; - mini = BigEndianBitConverter.GetBytes(entry.offset); - for(int i = 0; i < bytesPerHunk; i++) hunk[i] = mini[i % 8]; - - break; - case Chdv3EntryFlags.SelfHunk: return GetHunk(entry.offset); - case Chdv3EntryFlags.ParentHunk: - throw new ImageNotSupportedException("Parent images are not supported"); - case Chdv3EntryFlags.SecondCompressed: - throw new ImageNotSupportedException("FLAC is not supported"); - default: - throw new ImageNotSupportedException($"Hunk type {entry.flags & 0xF} is not supported"); - } - - break; - case 5: - if(hdrCompression == 0) - { - hunk = new byte[bytesPerHunk]; - imageStream.Seek(hunkTableSmall[hunkNo] * bytesPerHunk, SeekOrigin.Begin); - imageStream.Read(hunk, 0, hunk.Length); - } - else throw new ImageNotSupportedException("Compressed v5 hunks not yet supported"); - - break; - default: throw new ImageNotSupportedException($"Unsupported hunk map version {mapVersion}"); - } - - if(hunkCache.Count >= maxBlockCache) hunkCache.Clear(); - - hunkCache.Add(hunkNo, hunk); - - return hunk; - } - - enum ChdCompression : uint - { - None = 0, - Zlib = 1, - ZlibPlus = 2, - Av = 3 - } - - enum ChdFlags : uint - { - HasParent = 1, - Writable = 2 - } - - enum Chdv3EntryFlags : byte - { - /// <summary>Invalid</summary> - Invalid = 0, - /// <summary>Compressed with primary codec</summary> - Compressed = 1, - /// <summary>Uncompressed</summary> - Uncompressed = 2, - /// <summary>Use offset as data</summary> - Mini = 3, - /// <summary>Same as another hunk in file</summary> - SelfHunk = 4, - /// <summary>Same as another hunk in parent</summary> - ParentHunk = 5, - /// <summary>Compressed with secondary codec (FLAC)</summary> - SecondCompressed = 6 - } - - enum ChdOldTrackType : uint - { - Mode1 = 0, - Mode1Raw, - Mode2, - Mode2Form1, - Mode2Form2, - Mode2FormMix, - Mode2Raw, - Audio - } - - enum ChdOldSubType : uint - { - Cooked = 0, - Raw, - None - } - - // Hunks are represented in a 64 bit integer with 44 bit as offset, 20 bits as length - // Sectors are fixed at 512 bytes/sector - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdHeaderV1 - { - /// <summary> - /// Magic identifier, 'MComprHD' - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] tag; - /// <summary> - /// Length of header - /// </summary> - public uint length; - /// <summary> - /// Image format version - /// </summary> - public uint version; - /// <summary> - /// Image flags, <see cref="ChdFlags" /> - /// </summary> - public uint flags; - /// <summary> - /// Compression algorithm, <see cref="ChdCompression" /> - /// </summary> - public uint compression; - /// <summary> - /// Sectors per hunk - /// </summary> - public uint hunksize; - /// <summary> - /// Total # of hunk in image - /// </summary> - public uint totalhunks; - /// <summary> - /// Cylinders on disk - /// </summary> - public uint cylinders; - /// <summary> - /// Heads per cylinder - /// </summary> - public uint heads; - /// <summary> - /// Sectors per track - /// </summary> - public uint sectors; - /// <summary> - /// MD5 of raw data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] md5; - /// <summary> - /// MD5 of parent file - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] parentmd5; - } - - // Hunks are represented in a 64 bit integer with 44 bit as offset, 20 bits as length - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdHeaderV2 - { - /// <summary> - /// Magic identifier, 'MComprHD' - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] tag; - /// <summary> - /// Length of header - /// </summary> - public uint length; - /// <summary> - /// Image format version - /// </summary> - public uint version; - /// <summary> - /// Image flags, <see cref="ChdFlags" /> - /// </summary> - public uint flags; - /// <summary> - /// Compression algorithm, <see cref="ChdCompression" /> - /// </summary> - public uint compression; - /// <summary> - /// Sectors per hunk - /// </summary> - public uint hunksize; - /// <summary> - /// Total # of hunk in image - /// </summary> - public uint totalhunks; - /// <summary> - /// Cylinders on disk - /// </summary> - public uint cylinders; - /// <summary> - /// Heads per cylinder - /// </summary> - public uint heads; - /// <summary> - /// Sectors per track - /// </summary> - public uint sectors; - /// <summary> - /// MD5 of raw data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] md5; - /// <summary> - /// MD5 of parent file - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] parentmd5; - /// <summary> - /// Bytes per sector - /// </summary> - public uint seclen; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdHeaderV3 - { - /// <summary> - /// Magic identifier, 'MComprHD' - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] tag; - /// <summary> - /// Length of header - /// </summary> - public uint length; - /// <summary> - /// Image format version - /// </summary> - public uint version; - /// <summary> - /// Image flags, <see cref="ChdFlags" /> - /// </summary> - public uint flags; - /// <summary> - /// Compression algorithm, <see cref="ChdCompression" /> - /// </summary> - public uint compression; - /// <summary> - /// Total # of hunk in image - /// </summary> - public uint totalhunks; - /// <summary> - /// Total bytes in image - /// </summary> - public ulong logicalbytes; - /// <summary> - /// Offset to first metadata blob - /// </summary> - public ulong metaoffset; - /// <summary> - /// MD5 of raw data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] md5; - /// <summary> - /// MD5 of parent file - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] parentmd5; - /// <summary> - /// Bytes per hunk - /// </summary> - public uint hunkbytes; - /// <summary> - /// SHA1 of raw data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] sha1; - /// <summary> - /// SHA1 of parent file - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] parentsha1; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdMapV3Entry - { - /// <summary> - /// Offset to hunk from start of image - /// </summary> - public ulong offset; - /// <summary> - /// CRC32 of uncompressed hunk - /// </summary> - public uint crc; - /// <summary> - /// Lower 16 bits of length - /// </summary> - public ushort lengthLsb; - /// <summary> - /// Upper 8 bits of length - /// </summary> - public byte length; - /// <summary> - /// Hunk flags - /// </summary> - public byte flags; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdTrackOld - { - public uint type; - public uint subType; - public uint dataSize; - public uint subSize; - public uint frames; - public uint extraFrames; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdHeaderV4 - { - /// <summary> - /// Magic identifier, 'MComprHD' - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] tag; - /// <summary> - /// Length of header - /// </summary> - public uint length; - /// <summary> - /// Image format version - /// </summary> - public uint version; - /// <summary> - /// Image flags, <see cref="ChdFlags" /> - /// </summary> - public uint flags; - /// <summary> - /// Compression algorithm, <see cref="ChdCompression" /> - /// </summary> - public uint compression; - /// <summary> - /// Total # of hunk in image - /// </summary> - public uint totalhunks; - /// <summary> - /// Total bytes in image - /// </summary> - public ulong logicalbytes; - /// <summary> - /// Offset to first metadata blob - /// </summary> - public ulong metaoffset; - /// <summary> - /// Bytes per hunk - /// </summary> - public uint hunkbytes; - /// <summary> - /// SHA1 of raw+meta data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] sha1; - /// <summary> - /// SHA1 of parent file - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] parentsha1; - /// <summary> - /// SHA1 of raw data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] rawsha1; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdHeaderV5 - { - /// <summary> - /// Magic identifier, 'MComprHD' - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] tag; - /// <summary> - /// Length of header - /// </summary> - public uint length; - /// <summary> - /// Image format version - /// </summary> - public uint version; - /// <summary> - /// Compressor 0 - /// </summary> - public uint compressor0; - /// <summary> - /// Compressor 1 - /// </summary> - public uint compressor1; - /// <summary> - /// Compressor 2 - /// </summary> - public uint compressor2; - /// <summary> - /// Compressor 3 - /// </summary> - public uint compressor3; - /// <summary> - /// Total bytes in image - /// </summary> - public ulong logicalbytes; - /// <summary> - /// Offset to hunk map - /// </summary> - public ulong mapoffset; - /// <summary> - /// Offset to first metadata blob - /// </summary> - public ulong metaoffset; - /// <summary> - /// Bytes per hunk - /// </summary> - public uint hunkbytes; - /// <summary> - /// Bytes per unit within hunk - /// </summary> - public uint unitbytes; - /// <summary> - /// SHA1 of raw data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] rawsha1; - /// <summary> - /// SHA1 of raw+meta data - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] sha1; - /// <summary> - /// SHA1 of parent file - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] parentsha1; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdCompressedMapHeaderV5 - { - /// <summary> - /// Length of compressed map - /// </summary> - public uint length; - /// <summary> - /// Offset of first block (48 bits) and CRC16 of map (16 bits) - /// </summary> - public ulong startAndCrc; - /// <summary> - /// Bits used to encode compressed length on map entry - /// </summary> - public byte bitsUsedToEncodeCompLength; - /// <summary> - /// Bits used to encode self-refs - /// </summary> - public byte bitsUsedToEncodeSelfRefs; - /// <summary> - /// Bits used to encode parent unit refs - /// </summary> - public byte bitsUsedToEncodeParentUnits; - public byte reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdMapV5Entry - { - /// <summary> - /// Compression (8 bits) and length (24 bits) - /// </summary> - public uint compAndLength; - /// <summary> - /// Offset (48 bits) and CRC (16 bits) - /// </summary> - public ulong offsetAndCrc; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChdMetadataHeader - { - public uint tag; - public uint flagsAndLength; - public ulong next; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct HunkSector - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] - public ulong[] hunkEntry; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct HunkSectorSmall - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] - public uint[] hunkEntry; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CHD/Structs.cs b/DiscImageChef.DiscImages/CHD/Structs.cs new file mode 100644 index 000000000..36c30ef9b --- /dev/null +++ b/DiscImageChef.DiscImages/CHD/Structs.cs @@ -0,0 +1,443 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for MAME Compressed Hunks of Data disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Chd + { + // Hunks are represented in a 64 bit integer with 44 bit as offset, 20 bits as length + // Sectors are fixed at 512 bytes/sector + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdHeaderV1 + { + /// <summary> + /// Magic identifier, 'MComprHD' + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// <summary> + /// Length of header + /// </summary> + public uint length; + /// <summary> + /// Image format version + /// </summary> + public uint version; + /// <summary> + /// Image flags, <see cref="ChdFlags" /> + /// </summary> + public uint flags; + /// <summary> + /// Compression algorithm, <see cref="ChdCompression" /> + /// </summary> + public uint compression; + /// <summary> + /// Sectors per hunk + /// </summary> + public uint hunksize; + /// <summary> + /// Total # of hunk in image + /// </summary> + public uint totalhunks; + /// <summary> + /// Cylinders on disk + /// </summary> + public uint cylinders; + /// <summary> + /// Heads per cylinder + /// </summary> + public uint heads; + /// <summary> + /// Sectors per track + /// </summary> + public uint sectors; + /// <summary> + /// MD5 of raw data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] md5; + /// <summary> + /// MD5 of parent file + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] parentmd5; + } + + // Hunks are represented in a 64 bit integer with 44 bit as offset, 20 bits as length + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdHeaderV2 + { + /// <summary> + /// Magic identifier, 'MComprHD' + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// <summary> + /// Length of header + /// </summary> + public uint length; + /// <summary> + /// Image format version + /// </summary> + public uint version; + /// <summary> + /// Image flags, <see cref="ChdFlags" /> + /// </summary> + public uint flags; + /// <summary> + /// Compression algorithm, <see cref="ChdCompression" /> + /// </summary> + public uint compression; + /// <summary> + /// Sectors per hunk + /// </summary> + public uint hunksize; + /// <summary> + /// Total # of hunk in image + /// </summary> + public uint totalhunks; + /// <summary> + /// Cylinders on disk + /// </summary> + public uint cylinders; + /// <summary> + /// Heads per cylinder + /// </summary> + public uint heads; + /// <summary> + /// Sectors per track + /// </summary> + public uint sectors; + /// <summary> + /// MD5 of raw data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] md5; + /// <summary> + /// MD5 of parent file + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] parentmd5; + /// <summary> + /// Bytes per sector + /// </summary> + public uint seclen; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdHeaderV3 + { + /// <summary> + /// Magic identifier, 'MComprHD' + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// <summary> + /// Length of header + /// </summary> + public uint length; + /// <summary> + /// Image format version + /// </summary> + public uint version; + /// <summary> + /// Image flags, <see cref="ChdFlags" /> + /// </summary> + public uint flags; + /// <summary> + /// Compression algorithm, <see cref="ChdCompression" /> + /// </summary> + public uint compression; + /// <summary> + /// Total # of hunk in image + /// </summary> + public uint totalhunks; + /// <summary> + /// Total bytes in image + /// </summary> + public ulong logicalbytes; + /// <summary> + /// Offset to first metadata blob + /// </summary> + public ulong metaoffset; + /// <summary> + /// MD5 of raw data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] md5; + /// <summary> + /// MD5 of parent file + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] parentmd5; + /// <summary> + /// Bytes per hunk + /// </summary> + public uint hunkbytes; + /// <summary> + /// SHA1 of raw data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] sha1; + /// <summary> + /// SHA1 of parent file + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] parentsha1; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdMapV3Entry + { + /// <summary> + /// Offset to hunk from start of image + /// </summary> + public ulong offset; + /// <summary> + /// CRC32 of uncompressed hunk + /// </summary> + public uint crc; + /// <summary> + /// Lower 16 bits of length + /// </summary> + public ushort lengthLsb; + /// <summary> + /// Upper 8 bits of length + /// </summary> + public byte length; + /// <summary> + /// Hunk flags + /// </summary> + public byte flags; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdTrackOld + { + public uint type; + public uint subType; + public uint dataSize; + public uint subSize; + public uint frames; + public uint extraFrames; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdHeaderV4 + { + /// <summary> + /// Magic identifier, 'MComprHD' + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// <summary> + /// Length of header + /// </summary> + public uint length; + /// <summary> + /// Image format version + /// </summary> + public uint version; + /// <summary> + /// Image flags, <see cref="ChdFlags" /> + /// </summary> + public uint flags; + /// <summary> + /// Compression algorithm, <see cref="ChdCompression" /> + /// </summary> + public uint compression; + /// <summary> + /// Total # of hunk in image + /// </summary> + public uint totalhunks; + /// <summary> + /// Total bytes in image + /// </summary> + public ulong logicalbytes; + /// <summary> + /// Offset to first metadata blob + /// </summary> + public ulong metaoffset; + /// <summary> + /// Bytes per hunk + /// </summary> + public uint hunkbytes; + /// <summary> + /// SHA1 of raw+meta data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] sha1; + /// <summary> + /// SHA1 of parent file + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] parentsha1; + /// <summary> + /// SHA1 of raw data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] rawsha1; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdHeaderV5 + { + /// <summary> + /// Magic identifier, 'MComprHD' + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// <summary> + /// Length of header + /// </summary> + public uint length; + /// <summary> + /// Image format version + /// </summary> + public uint version; + /// <summary> + /// Compressor 0 + /// </summary> + public uint compressor0; + /// <summary> + /// Compressor 1 + /// </summary> + public uint compressor1; + /// <summary> + /// Compressor 2 + /// </summary> + public uint compressor2; + /// <summary> + /// Compressor 3 + /// </summary> + public uint compressor3; + /// <summary> + /// Total bytes in image + /// </summary> + public ulong logicalbytes; + /// <summary> + /// Offset to hunk map + /// </summary> + public ulong mapoffset; + /// <summary> + /// Offset to first metadata blob + /// </summary> + public ulong metaoffset; + /// <summary> + /// Bytes per hunk + /// </summary> + public uint hunkbytes; + /// <summary> + /// Bytes per unit within hunk + /// </summary> + public uint unitbytes; + /// <summary> + /// SHA1 of raw data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] rawsha1; + /// <summary> + /// SHA1 of raw+meta data + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] sha1; + /// <summary> + /// SHA1 of parent file + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] parentsha1; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdCompressedMapHeaderV5 + { + /// <summary> + /// Length of compressed map + /// </summary> + public uint length; + /// <summary> + /// Offset of first block (48 bits) and CRC16 of map (16 bits) + /// </summary> + public ulong startAndCrc; + /// <summary> + /// Bits used to encode compressed length on map entry + /// </summary> + public byte bitsUsedToEncodeCompLength; + /// <summary> + /// Bits used to encode self-refs + /// </summary> + public byte bitsUsedToEncodeSelfRefs; + /// <summary> + /// Bits used to encode parent unit refs + /// </summary> + public byte bitsUsedToEncodeParentUnits; + public byte reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdMapV5Entry + { + /// <summary> + /// Compression (8 bits) and length (24 bits) + /// </summary> + public uint compAndLength; + /// <summary> + /// Offset (48 bits) and CRC (16 bits) + /// </summary> + public ulong offsetAndCrc; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChdMetadataHeader + { + public uint tag; + public uint flagsAndLength; + public ulong next; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HunkSector + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public ulong[] hunkEntry; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HunkSectorSmall + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public uint[] hunkEntry; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK/CPCDSK.cs b/DiscImageChef.DiscImages/CPCDSK/CPCDSK.cs new file mode 100644 index 000000000..f2e6f483c --- /dev/null +++ b/DiscImageChef.DiscImages/CPCDSK/CPCDSK.cs @@ -0,0 +1,74 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CPCDSK.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages CPCEMU disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Cpcdsk : IMediaImage + { + Dictionary<ulong, byte[]> addressMarks; + bool extended; + ImageInfo imageInfo; + Dictionary<ulong, byte[]> sectors; + + public Cpcdsk() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK/Constants.cs b/DiscImageChef.DiscImages/CPCDSK/Constants.cs new file mode 100644 index 000000000..961deab1e --- /dev/null +++ b/DiscImageChef.DiscImages/CPCDSK/Constants.cs @@ -0,0 +1,66 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for CPCEMU disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Cpcdsk + { + /// <summary> + /// Identifier for CPCEMU disk images, "MV - CPCEMU Disk-File" + /// </summary> + readonly byte[] cpcdskId = + { + 0x4D, 0x56, 0x20, 0x2D, 0x20, 0x43, 0x50, 0x43, 0x45, 0x4D, 0x55, 0x20, 0x44, 0x69, 0x73, 0x6B, 0x2D, 0x46, + 0x69, 0x6C, 0x65 + }; + /// <summary> + /// Identifier for DU54 disk images, "MV - CPC format Disk Image (DU54)" + /// </summary> + readonly byte[] du54Id = + { + 0x4D, 0x56, 0x20, 0x2D, 0x20, 0x43, 0x50, 0x43, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x20, 0x44, 0x69, + 0x73, 0x6B, 0x20 + }; + /// <summary> + /// Identifier for Extended CPCEMU disk images, "EXTENDED CPC DSK File" + /// </summary> + readonly byte[] edskId = + { + 0x45, 0x58, 0x54, 0x45, 0x4E, 0x44, 0x45, 0x44, 0x20, 0x43, 0x50, 0x43, 0x20, 0x44, 0x53, 0x4B, 0x20, 0x46, + 0x69, 0x6C, 0x65 + }; + /// <summary> + /// Identifier for track information, "Track-Info\r\n" + /// </summary> + readonly byte[] trackId = {0x54, 0x72, 0x61, 0x63, 0x6B, 0x2D, 0x49, 0x6E, 0x66, 0x6F}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK/Helpers.cs b/DiscImageChef.DiscImages/CPCDSK/Helpers.cs new file mode 100644 index 000000000..8e538ee9a --- /dev/null +++ b/DiscImageChef.DiscImages/CPCDSK/Helpers.cs @@ -0,0 +1,55 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for CPCEMU disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.Decoders.Floppy; + +namespace DiscImageChef.DiscImages +{ + public partial class Cpcdsk + { + static int SizeCodeToBytes(IBMSectorSizeCode code) + { + switch(code) + { + case IBMSectorSizeCode.EighthKilo: return 128; + case IBMSectorSizeCode.QuarterKilo: return 256; + case IBMSectorSizeCode.HalfKilo: return 512; + case IBMSectorSizeCode.Kilo: return 1024; + case IBMSectorSizeCode.TwiceKilo: return 2048; + case IBMSectorSizeCode.FriceKilo: return 4096; + case IBMSectorSizeCode.TwiceFriceKilo: return 8192; + case IBMSectorSizeCode.FricelyFriceKilo: return 16384; + default: return 0; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK/Identify.cs b/DiscImageChef.DiscImages/CPCDSK/Identify.cs new file mode 100644 index 000000000..d6dd456f7 --- /dev/null +++ b/DiscImageChef.DiscImages/CPCDSK/Identify.cs @@ -0,0 +1,65 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies CPCEMU disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Cpcdsk + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] headerB = new byte[256]; + stream.Read(headerB, 0, 256); + IntPtr headerPtr = Marshal.AllocHGlobal(256); + Marshal.Copy(headerB, 0, headerPtr, 256); + CpcDiskInfo header = (CpcDiskInfo)Marshal.PtrToStructure(headerPtr, typeof(CpcDiskInfo)); + Marshal.FreeHGlobal(headerPtr); + + DicConsole.DebugWriteLine("CPCDSK plugin", "header.magic = \"{0}\"", + StringHandlers.CToString(header.magic)); + + return cpcdskId.SequenceEqual(header.magic) || edskId.SequenceEqual(header.magic) || + du54Id.SequenceEqual(header.magic); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK/Properties.cs b/DiscImageChef.DiscImages/CPCDSK/Properties.cs new file mode 100644 index 000000000..54b86f9bb --- /dev/null +++ b/DiscImageChef.DiscImages/CPCDSK/Properties.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for CPCEMU disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Cpcdsk + { + public ImageInfo Info => imageInfo; + public string Name => "CPCEMU Disk-File and Extended CPC Disk-File"; + public Guid Id => new Guid("724B16CC-ADB9-492E-BA07-CAEEC1012B16"); + public string Format => extended ? "CPCEMU Extended disk image" : "CPCEMU disk image"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK.cs b/DiscImageChef.DiscImages/CPCDSK/Read.cs similarity index 55% rename from DiscImageChef.DiscImages/CPCDSK.cs rename to DiscImageChef.DiscImages/CPCDSK/Read.cs index 01d2c38bc..0ac177193 100644 --- a/DiscImageChef.DiscImages/CPCDSK.cs +++ b/DiscImageChef.DiscImages/CPCDSK/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : CPCDSK.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages CPCEMU disk images. +// Reads CPCEMU disk images. // // --[ License ] -------------------------------------------------------------- // @@ -40,113 +40,13 @@ using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Decoders.Floppy; -using Schemas; namespace DiscImageChef.DiscImages { - public class Cpcdsk : IMediaImage + public partial class Cpcdsk { - /// <summary> - /// Identifier for CPCEMU disk images, "MV - CPCEMU Disk-File" - /// </summary> - readonly byte[] cpcdskId = - { - 0x4D, 0x56, 0x20, 0x2D, 0x20, 0x43, 0x50, 0x43, 0x45, 0x4D, 0x55, 0x20, 0x44, 0x69, 0x73, 0x6B, 0x2D, 0x46, - 0x69, 0x6C, 0x65 - }; - /// <summary> - /// Identifier for DU54 disk images, "MV - CPC format Disk Image (DU54)" - /// </summary> - readonly byte[] du54Id = - { - 0x4D, 0x56, 0x20, 0x2D, 0x20, 0x43, 0x50, 0x43, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x20, 0x44, 0x69, - 0x73, 0x6B, 0x20 - }; - /// <summary> - /// Identifier for Extended CPCEMU disk images, "EXTENDED CPC DSK File" - /// </summary> - readonly byte[] edskId = - { - 0x45, 0x58, 0x54, 0x45, 0x4E, 0x44, 0x45, 0x44, 0x20, 0x43, 0x50, 0x43, 0x20, 0x44, 0x53, 0x4B, 0x20, 0x46, - 0x69, 0x6C, 0x65 - }; - /// <summary> - /// Identifier for track information, "Track-Info\r\n" - /// </summary> - readonly byte[] trackId = {0x54, 0x72, 0x61, 0x63, 0x6B, 0x2D, 0x49, 0x6E, 0x66, 0x6F}; - Dictionary<ulong, byte[]> addressMarks; - - bool extended; - ImageInfo imageInfo; - Dictionary<ulong, byte[]> sectors; - - public Cpcdsk() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "CPCEMU Disk-File and Extended CPC Disk-File"; - public Guid Id => new Guid("724B16CC-ADB9-492E-BA07-CAEEC1012B16"); - - public string Format => extended ? "CPCEMU Extended disk image" : "CPCEMU disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] headerB = new byte[256]; - stream.Read(headerB, 0, 256); - IntPtr headerPtr = Marshal.AllocHGlobal(256); - Marshal.Copy(headerB, 0, headerPtr, 256); - CpcDiskInfo header = (CpcDiskInfo)Marshal.PtrToStructure(headerPtr, typeof(CpcDiskInfo)); - Marshal.FreeHGlobal(headerPtr); - - DicConsole.DebugWriteLine("CPCDSK plugin", "header.magic = \"{0}\"", - StringHandlers.CToString(header.magic)); - - return cpcdskId.SequenceEqual(header.magic) || edskId.SequenceEqual(header.magic) || - du54Id.SequenceEqual(header.magic); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -416,238 +316,5 @@ namespace DiscImageChef.DiscImages return ms.ToArray(); } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - static int SizeCodeToBytes(IBMSectorSizeCode code) - { - switch(code) - { - case IBMSectorSizeCode.EighthKilo: return 128; - case IBMSectorSizeCode.QuarterKilo: return 256; - case IBMSectorSizeCode.HalfKilo: return 512; - case IBMSectorSizeCode.Kilo: return 1024; - case IBMSectorSizeCode.TwiceKilo: return 2048; - case IBMSectorSizeCode.FriceKilo: return 4096; - case IBMSectorSizeCode.TwiceFriceKilo: return 8192; - case IBMSectorSizeCode.FricelyFriceKilo: return 16384; - default: return 0; - } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CpcDiskInfo - { - /// <summary> - /// Magic number, "MV - CPCEMU Disk-File" in old files, "EXTENDED CPC DSK File" in extended ones - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] - public byte[] magic; - /// <summary> - /// Second part of magic, should be "\r\nDisk-Info\r\n" in all, but some emulators write spaces instead. - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - public byte[] magic2; - /// <summary> - /// Creator application (can be null) - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] - public byte[] creator; - /// <summary> - /// Tracks - /// </summary> - public byte tracks; - /// <summary> - /// Sides - /// </summary> - public byte sides; - /// <summary> - /// Size of a track including the 256 bytes header. Unused by extended format, as this format includes a table in the - /// next field - /// </summary> - public ushort tracksize; - /// <summary> - /// Size of each track in the extended format. 0 indicates track is not formatted and not present in image. - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 204)] - public byte[] tracksizeTable; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CpcTrackInfo - { - /// <summary> - /// Magic number, "Track-Info\r\n" - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] - public byte[] magic; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public byte[] carriageReturn; - /// <summary> - /// Padding - /// </summary> - public uint padding; - /// <summary> - /// Track number - /// </summary> - public byte track; - /// <summary> - /// Side number - /// </summary> - public byte side; - /// <summary> - /// Controller data rate - /// </summary> - public byte dataRate; - /// <summary> - /// Recording mode - /// </summary> - public byte recordingMode; - /// <summary> - /// Bytes per sector - /// </summary> - public IBMSectorSizeCode bps; - /// <summary> - /// How many sectors in this track - /// </summary> - public byte sectors; - /// <summary> - /// GAP#3 - /// </summary> - public byte gap3; - /// <summary> - /// Filler - /// </summary> - public byte filler; - /// <summary> - /// Informatino for up to 32 sectors - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public CpcSectorInfo[] sectorsInfo; - } - - /// <summary> - /// Sector information - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CpcSectorInfo - { - /// <summary> - /// Track number from address mark - /// </summary> - public byte track; - /// <summary> - /// Side number from address mark - /// </summary> - public byte side; - /// <summary> - /// Sector ID from address mark - /// </summary> - public byte id; - /// <summary> - /// Sector size from address mark - /// </summary> - public IBMSectorSizeCode size; - /// <summary> - /// ST1 register from controller - /// </summary> - public byte st1; - /// <summary> - /// ST2 register from controller - /// </summary> - public byte st2; - /// <summary> - /// Length in bytes of this sector size. If it is bigger than expected sector size, it's a weak sector read several - /// times. - /// </summary> - public ushort len; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK/Structs.cs b/DiscImageChef.DiscImages/CPCDSK/Structs.cs new file mode 100644 index 000000000..680eee68e --- /dev/null +++ b/DiscImageChef.DiscImages/CPCDSK/Structs.cs @@ -0,0 +1,168 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for CPCEMU disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; +using DiscImageChef.Decoders.Floppy; + +namespace DiscImageChef.DiscImages +{ + public partial class Cpcdsk + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CpcDiskInfo + { + /// <summary> + /// Magic number, "MV - CPCEMU Disk-File" in old files, "EXTENDED CPC DSK File" in extended ones + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public byte[] magic; + /// <summary> + /// Second part of magic, should be "\r\nDisk-Info\r\n" in all, but some emulators write spaces instead. + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public byte[] magic2; + /// <summary> + /// Creator application (can be null) + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] + public byte[] creator; + /// <summary> + /// Tracks + /// </summary> + public byte tracks; + /// <summary> + /// Sides + /// </summary> + public byte sides; + /// <summary> + /// Size of a track including the 256 bytes header. Unused by extended format, as this format includes a table in the + /// next field + /// </summary> + public ushort tracksize; + /// <summary> + /// Size of each track in the extended format. 0 indicates track is not formatted and not present in image. + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 204)] + public byte[] tracksizeTable; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CpcTrackInfo + { + /// <summary> + /// Magic number, "Track-Info\r\n" + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public byte[] magic; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] carriageReturn; + /// <summary> + /// Padding + /// </summary> + public uint padding; + /// <summary> + /// Track number + /// </summary> + public byte track; + /// <summary> + /// Side number + /// </summary> + public byte side; + /// <summary> + /// Controller data rate + /// </summary> + public byte dataRate; + /// <summary> + /// Recording mode + /// </summary> + public byte recordingMode; + /// <summary> + /// Bytes per sector + /// </summary> + public IBMSectorSizeCode bps; + /// <summary> + /// How many sectors in this track + /// </summary> + public byte sectors; + /// <summary> + /// GAP#3 + /// </summary> + public byte gap3; + /// <summary> + /// Filler + /// </summary> + public byte filler; + /// <summary> + /// Informatino for up to 32 sectors + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public CpcSectorInfo[] sectorsInfo; + } + + /// <summary> + /// Sector information + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CpcSectorInfo + { + /// <summary> + /// Track number from address mark + /// </summary> + public byte track; + /// <summary> + /// Side number from address mark + /// </summary> + public byte side; + /// <summary> + /// Sector ID from address mark + /// </summary> + public byte id; + /// <summary> + /// Sector size from address mark + /// </summary> + public IBMSectorSizeCode size; + /// <summary> + /// ST1 register from controller + /// </summary> + public byte st1; + /// <summary> + /// ST2 register from controller + /// </summary> + public byte st2; + /// <summary> + /// Length in bytes of this sector size. If it is bigger than expected sector size, it's a weak sector read several + /// times. + /// </summary> + public ushort len; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CPCDSK/Unsupported.cs b/DiscImageChef.DiscImages/CPCDSK/Unsupported.cs new file mode 100644 index 000000000..c030a9154 --- /dev/null +++ b/DiscImageChef.DiscImages/CPCDSK/Unsupported.cs @@ -0,0 +1,128 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by CPCEMU disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Cpcdsk + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy.cs b/DiscImageChef.DiscImages/CisCopy.cs deleted file mode 100644 index 2e55799fc..000000000 --- a/DiscImageChef.DiscImages/CisCopy.cs +++ /dev/null @@ -1,707 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : CisCopy.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages CisCopy disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - /* This is a very simple format created by a German application called CisCopy, aka CCOPY.EXE, with extension .DCF. - * First byte indicates the floppy type, limited to standard formats. - * Indeed if the floppy is not DOS formatted, user must choose from the list of supported formats manually. - * Next 80 bytes (for 5.25" DD disks) or 160 bytes (for 5.25" HD and 3.5" disks) indicate if a track has been copied - * or not. - * It offers three copy methods: - * a) All, copies all tracks - * b) FAT, copies all tracks which contain sectors marked as sued by FAT - * c) "Belelung" similarly to FAT. On some disk tests FAT cuts data, while belelung does not. - * Finally, next byte indicates compression: - * 0) No compression - * 1) Normal compression, algorithm unknown - * 2) High compression, algorithm unknown - * Then the data for whole tracks follow. - */ - public class CisCopy : IWritableImage - { - byte[] decodedDisk; - ImageInfo imageInfo; - long writingOffset; - FileStream writingStream; - - public CisCopy() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "CisCopy Disk Image (DC-File)"; - public Guid Id => new Guid("EDF20CC7-6012-49E2-9E92-663A53E42130"); - - public string Format => "CisCopy"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - public ImageInfo Info => imageInfo; - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - DiskType type = (DiskType)stream.ReadByte(); - byte tracks; - - switch(type) - { - case DiskType.MD1DD8: - case DiskType.MD1DD: - case DiskType.MD2DD8: - case DiskType.MD2DD: - tracks = 80; - break; - case DiskType.MF2DD: - case DiskType.MD2HD: - case DiskType.MF2HD: - tracks = 160; - break; - default: return false; - } - - byte[] trackBytes = new byte[tracks]; - stream.Read(trackBytes, 0, tracks); - - for(int i = 0; i < tracks; i++) - if(trackBytes[i] != (byte)TrackType.Copied && trackBytes[i] != (byte)TrackType.Omitted && - trackBytes[i] != (byte)TrackType.OmittedAlternate) - return false; - - Compression cmpr = (Compression)stream.ReadByte(); - - if(cmpr != Compression.None && cmpr != Compression.Normal && cmpr != Compression.High) return false; - - switch(type) - { - case DiskType.MD1DD8: - if(stream.Length > 40 * 1 * 8 * 512 + 82) return false; - - break; - case DiskType.MD1DD: - if(stream.Length > 40 * 1 * 9 * 512 + 82) return false; - - break; - case DiskType.MD2DD8: - if(stream.Length > 40 * 2 * 8 * 512 + 82) return false; - - break; - case DiskType.MD2DD: - if(stream.Length > 40 * 2 * 9 * 512 + 82) return false; - - break; - case DiskType.MF2DD: - if(stream.Length > 80 * 2 * 9 * 512 + 162) return false; - - break; - case DiskType.MD2HD: - if(stream.Length > 80 * 2 * 15 * 512 + 162) return false; - - break; - case DiskType.MF2HD: - if(stream.Length > 80 * 2 * 18 * 512 + 162) return false; - - break; - } - - return true; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - DiskType type = (DiskType)stream.ReadByte(); - byte tracks; - - switch(type) - { - case DiskType.MD1DD8: - case DiskType.MD1DD: - case DiskType.MD2DD8: - case DiskType.MD2DD: - tracks = 80; - break; - case DiskType.MF2DD: - case DiskType.MD2HD: - case DiskType.MF2HD: - tracks = 160; - break; - default: throw new ImageNotSupportedException($"Incorrect disk type {(byte)type}"); - } - - byte[] trackBytes = new byte[tracks]; - stream.Read(trackBytes, 0, tracks); - - Compression cmpr = (Compression)stream.ReadByte(); - - if(cmpr != Compression.None) - throw new FeatureSupportedButNotImplementedImageException("Compressed images are not supported."); - - int tracksize = 0; - - switch(type) - { - case DiskType.MD1DD8: - case DiskType.MD2DD8: - tracksize = 8 * 512; - break; - case DiskType.MD1DD: - case DiskType.MD2DD: - case DiskType.MF2DD: - tracksize = 9 * 512; - break; - case DiskType.MD2HD: - tracksize = 15 * 512; - break; - case DiskType.MF2HD: - tracksize = 18 * 512; - break; - } - - int headstep = 1; - if(type == DiskType.MD1DD || type == DiskType.MD1DD8) headstep = 2; - - MemoryStream decodedImage = new MemoryStream(); - - for(int i = 0; i < tracks; i += headstep) - { - byte[] track = new byte[tracksize]; - - if((TrackType)trackBytes[i] == TrackType.Copied) stream.Read(track, 0, tracksize); - else ArrayHelpers.ArrayFill(track, (byte)0xF6); - - decodedImage.Write(track, 0, tracksize); - } - - imageInfo.Application = "CisCopy"; - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = imageFilter.GetFilename(); - imageInfo.ImageSize = (ulong)(stream.Length - 2 - trackBytes.Length); - imageInfo.SectorSize = 512; - - switch(type) - { - case DiskType.MD1DD8: - imageInfo.MediaType = MediaType.DOS_525_SS_DD_8; - imageInfo.Sectors = 40 * 1 * 8; - imageInfo.Heads = 1; - imageInfo.Cylinders = 40; - imageInfo.SectorsPerTrack = 8; - break; - case DiskType.MD2DD8: - imageInfo.MediaType = MediaType.DOS_525_DS_DD_8; - imageInfo.Sectors = 40 * 2 * 8; - imageInfo.Heads = 2; - imageInfo.Cylinders = 40; - imageInfo.SectorsPerTrack = 8; - break; - case DiskType.MD1DD: - imageInfo.MediaType = MediaType.DOS_525_SS_DD_9; - imageInfo.Sectors = 40 * 1 * 9; - imageInfo.Heads = 1; - imageInfo.Cylinders = 40; - imageInfo.SectorsPerTrack = 9; - break; - case DiskType.MD2DD: - imageInfo.MediaType = MediaType.DOS_525_DS_DD_9; - imageInfo.Sectors = 40 * 2 * 9; - imageInfo.Heads = 2; - imageInfo.Cylinders = 40; - imageInfo.SectorsPerTrack = 9; - break; - case DiskType.MF2DD: - imageInfo.MediaType = MediaType.DOS_35_DS_DD_9; - imageInfo.Sectors = 80 * 2 * 9; - imageInfo.Heads = 2; - imageInfo.Cylinders = 80; - imageInfo.SectorsPerTrack = 9; - break; - case DiskType.MD2HD: - imageInfo.MediaType = MediaType.DOS_525_HD; - imageInfo.Sectors = 80 * 2 * 15; - imageInfo.Heads = 2; - imageInfo.Cylinders = 80; - imageInfo.SectorsPerTrack = 15; - break; - case DiskType.MF2HD: - imageInfo.MediaType = MediaType.DOS_35_HD; - imageInfo.Sectors = 80 * 2 * 18; - imageInfo.Heads = 2; - imageInfo.Cylinders = 80; - imageInfo.SectorsPerTrack = 18; - break; - } - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - decodedDisk = decodedImage.ToArray(); - - decodedImage.Close(); - - DicConsole.VerboseWriteLine("CisCopy image contains a disk of type {0}", imageInfo.MediaType); - - return true; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Array.Copy(decodedDisk, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, - length * imageInfo.SectorSize); - - return buffer; - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - // TODO: Test with real hardware to see real supported media - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, - MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9 - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".dcf"}; - - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - DiskType diskType; - switch(mediaType) - { - case MediaType.DOS_35_DS_DD_9: - diskType = DiskType.MF2DD; - break; - case MediaType.DOS_35_HD: - diskType = DiskType.MF2HD; - break; - case MediaType.DOS_525_DS_DD_8: - diskType = DiskType.MD2DD8; - break; - case MediaType.DOS_525_DS_DD_9: - diskType = DiskType.MD2DD; - break; - case MediaType.DOS_525_HD: - diskType = DiskType.MD2HD; - break; - case MediaType.DOS_525_SS_DD_8: - diskType = DiskType.MD1DD8; - break; - case MediaType.DOS_525_SS_DD_9: - diskType = DiskType.MD1DD; - break; - default: - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - writingStream.WriteByte((byte)diskType); - - byte tracks = 0; - - switch(diskType) - { - case DiskType.MD1DD8: - case DiskType.MD1DD: - case DiskType.MD2DD8: - case DiskType.MD2DD: - tracks = 80; - break; - case DiskType.MF2DD: - case DiskType.MD2HD: - case DiskType.MF2HD: - tracks = 160; - break; - } - - int headstep = 1; - if(diskType == DiskType.MD1DD || diskType == DiskType.MD1DD8) headstep = 2; - - for(int i = 0; i < tracks; i += headstep) - { - writingStream.WriteByte((byte)TrackType.Copied); - if(headstep == 2) writingStream.WriteByte(0); - } - - writingStream.WriteByte((byte)Compression.None); - writingOffset = writingStream.Position; - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek(writingOffset + (long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek(writingOffset + (long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - enum DiskType : byte - { - MD1DD8 = 1, - MD1DD = 2, - MD2DD8 = 3, - MD2DD = 4, - MF2DD = 5, - MD2HD = 6, - MF2HD = 7 - } - - enum Compression : byte - { - None = 0, - Normal = 1, - High = 2 - } - - enum TrackType : byte - { - Copied = 0x4C, - Omitted = 0xFA, - OmittedAlternate = 0xFE - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy/CisCopy.cs b/DiscImageChef.DiscImages/CisCopy/CisCopy.cs new file mode 100644 index 000000000..038cac7b7 --- /dev/null +++ b/DiscImageChef.DiscImages/CisCopy/CisCopy.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CisCopy.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages CisCopy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + /* This is a very simple format created by a German application called CisCopy, aka CCOPY.EXE, with extension .DCF. + * First byte indicates the floppy type, limited to standard formats. + * Indeed if the floppy is not DOS formatted, user must choose from the list of supported formats manually. + * Next 80 bytes (for 5.25" DD disks) or 160 bytes (for 5.25" HD and 3.5" disks) indicate if a track has been copied + * or not. + * It offers three copy methods: + * a) All, copies all tracks + * b) FAT, copies all tracks which contain sectors marked as sued by FAT + * c) "Belelung" similarly to FAT. On some disk tests FAT cuts data, while belelung does not. + * Finally, next byte indicates compression: + * 0) No compression + * 1) Normal compression, algorithm unknown + * 2) High compression, algorithm unknown + * Then the data for whole tracks follow. + */ + public partial class CisCopy : IWritableImage + { + byte[] decodedDisk; + ImageInfo imageInfo; + long writingOffset; + FileStream writingStream; + + public CisCopy() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy/Enums.cs b/DiscImageChef.DiscImages/CisCopy/Enums.cs new file mode 100644 index 000000000..ec95b3be2 --- /dev/null +++ b/DiscImageChef.DiscImages/CisCopy/Enums.cs @@ -0,0 +1,65 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for CisCopy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics.CodeAnalysis; + +namespace DiscImageChef.DiscImages +{ + public partial class CisCopy + { + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum DiskType : byte + { + MD1DD8 = 1, + MD1DD = 2, + MD2DD8 = 3, + MD2DD = 4, + MF2DD = 5, + MD2HD = 6, + MF2HD = 7 + } + + enum Compression : byte + { + None = 0, + Normal = 1, + High = 2 + } + + enum TrackType : byte + { + Copied = 0x4C, + Omitted = 0xFA, + OmittedAlternate = 0xFE + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy/Identify.cs b/DiscImageChef.DiscImages/CisCopy/Identify.cs new file mode 100644 index 000000000..265f368bc --- /dev/null +++ b/DiscImageChef.DiscImages/CisCopy/Identify.cs @@ -0,0 +1,111 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies CisCopy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class CisCopy + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + DiskType type = (DiskType)stream.ReadByte(); + byte tracks; + + switch(type) + { + case DiskType.MD1DD8: + case DiskType.MD1DD: + case DiskType.MD2DD8: + case DiskType.MD2DD: + tracks = 80; + break; + case DiskType.MF2DD: + case DiskType.MD2HD: + case DiskType.MF2HD: + tracks = 160; + break; + default: return false; + } + + byte[] trackBytes = new byte[tracks]; + stream.Read(trackBytes, 0, tracks); + + for(int i = 0; i < tracks; i++) + if(trackBytes[i] != (byte)TrackType.Copied && trackBytes[i] != (byte)TrackType.Omitted && + trackBytes[i] != (byte)TrackType.OmittedAlternate) + return false; + + Compression cmpr = (Compression)stream.ReadByte(); + + if(cmpr != Compression.None && cmpr != Compression.Normal && cmpr != Compression.High) return false; + + switch(type) + { + case DiskType.MD1DD8: + if(stream.Length > 40 * 1 * 8 * 512 + 82) return false; + + break; + case DiskType.MD1DD: + if(stream.Length > 40 * 1 * 9 * 512 + 82) return false; + + break; + case DiskType.MD2DD8: + if(stream.Length > 40 * 2 * 8 * 512 + 82) return false; + + break; + case DiskType.MD2DD: + if(stream.Length > 40 * 2 * 9 * 512 + 82) return false; + + break; + case DiskType.MF2DD: + if(stream.Length > 80 * 2 * 9 * 512 + 162) return false; + + break; + case DiskType.MD2HD: + if(stream.Length > 80 * 2 * 15 * 512 + 162) return false; + + break; + case DiskType.MF2HD: + if(stream.Length > 80 * 2 * 18 * 512 + 162) return false; + + break; + } + + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy/Properties.cs b/DiscImageChef.DiscImages/CisCopy/Properties.cs new file mode 100644 index 000000000..b3fb996e0 --- /dev/null +++ b/DiscImageChef.DiscImages/CisCopy/Properties.cs @@ -0,0 +1,72 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for CisCopy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class CisCopy + { + public string Name => "CisCopy Disk Image (DC-File)"; + public Guid Id => new Guid("EDF20CC7-6012-49E2-9E92-663A53E42130"); + public string Format => "CisCopy"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public ImageInfo Info => imageInfo; + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + // TODO: Test with real hardware to see real supported media + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, + MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9 + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".dcf"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy/Read.cs b/DiscImageChef.DiscImages/CisCopy/Read.cs new file mode 100644 index 000000000..106d3f336 --- /dev/null +++ b/DiscImageChef.DiscImages/CisCopy/Read.cs @@ -0,0 +1,204 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads CisCopy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class CisCopy + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + DiskType type = (DiskType)stream.ReadByte(); + byte tracks; + + switch(type) + { + case DiskType.MD1DD8: + case DiskType.MD1DD: + case DiskType.MD2DD8: + case DiskType.MD2DD: + tracks = 80; + break; + case DiskType.MF2DD: + case DiskType.MD2HD: + case DiskType.MF2HD: + tracks = 160; + break; + default: throw new ImageNotSupportedException($"Incorrect disk type {(byte)type}"); + } + + byte[] trackBytes = new byte[tracks]; + stream.Read(trackBytes, 0, tracks); + + Compression cmpr = (Compression)stream.ReadByte(); + + if(cmpr != Compression.None) + throw new FeatureSupportedButNotImplementedImageException("Compressed images are not supported."); + + int tracksize = 0; + + switch(type) + { + case DiskType.MD1DD8: + case DiskType.MD2DD8: + tracksize = 8 * 512; + break; + case DiskType.MD1DD: + case DiskType.MD2DD: + case DiskType.MF2DD: + tracksize = 9 * 512; + break; + case DiskType.MD2HD: + tracksize = 15 * 512; + break; + case DiskType.MF2HD: + tracksize = 18 * 512; + break; + } + + int headstep = 1; + if(type == DiskType.MD1DD || type == DiskType.MD1DD8) headstep = 2; + + MemoryStream decodedImage = new MemoryStream(); + + for(int i = 0; i < tracks; i += headstep) + { + byte[] track = new byte[tracksize]; + + if((TrackType)trackBytes[i] == TrackType.Copied) stream.Read(track, 0, tracksize); + else ArrayHelpers.ArrayFill(track, (byte)0xF6); + + decodedImage.Write(track, 0, tracksize); + } + + imageInfo.Application = "CisCopy"; + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = imageFilter.GetFilename(); + imageInfo.ImageSize = (ulong)(stream.Length - 2 - trackBytes.Length); + imageInfo.SectorSize = 512; + + switch(type) + { + case DiskType.MD1DD8: + imageInfo.MediaType = MediaType.DOS_525_SS_DD_8; + imageInfo.Sectors = 40 * 1 * 8; + imageInfo.Heads = 1; + imageInfo.Cylinders = 40; + imageInfo.SectorsPerTrack = 8; + break; + case DiskType.MD2DD8: + imageInfo.MediaType = MediaType.DOS_525_DS_DD_8; + imageInfo.Sectors = 40 * 2 * 8; + imageInfo.Heads = 2; + imageInfo.Cylinders = 40; + imageInfo.SectorsPerTrack = 8; + break; + case DiskType.MD1DD: + imageInfo.MediaType = MediaType.DOS_525_SS_DD_9; + imageInfo.Sectors = 40 * 1 * 9; + imageInfo.Heads = 1; + imageInfo.Cylinders = 40; + imageInfo.SectorsPerTrack = 9; + break; + case DiskType.MD2DD: + imageInfo.MediaType = MediaType.DOS_525_DS_DD_9; + imageInfo.Sectors = 40 * 2 * 9; + imageInfo.Heads = 2; + imageInfo.Cylinders = 40; + imageInfo.SectorsPerTrack = 9; + break; + case DiskType.MF2DD: + imageInfo.MediaType = MediaType.DOS_35_DS_DD_9; + imageInfo.Sectors = 80 * 2 * 9; + imageInfo.Heads = 2; + imageInfo.Cylinders = 80; + imageInfo.SectorsPerTrack = 9; + break; + case DiskType.MD2HD: + imageInfo.MediaType = MediaType.DOS_525_HD; + imageInfo.Sectors = 80 * 2 * 15; + imageInfo.Heads = 2; + imageInfo.Cylinders = 80; + imageInfo.SectorsPerTrack = 15; + break; + case DiskType.MF2HD: + imageInfo.MediaType = MediaType.DOS_35_HD; + imageInfo.Sectors = 80 * 2 * 18; + imageInfo.Heads = 2; + imageInfo.Cylinders = 80; + imageInfo.SectorsPerTrack = 18; + break; + } + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + decodedDisk = decodedImage.ToArray(); + + decodedImage.Close(); + + DicConsole.VerboseWriteLine("CisCopy image contains a disk of type {0}", imageInfo.MediaType); + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Array.Copy(decodedDisk, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, + length * imageInfo.SectorSize); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy/Unsupported.cs b/DiscImageChef.DiscImages/CisCopy/Unsupported.cs new file mode 100644 index 000000000..c9d2aecfa --- /dev/null +++ b/DiscImageChef.DiscImages/CisCopy/Unsupported.cs @@ -0,0 +1,144 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by CisCopy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class CisCopy + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CisCopy/Write.cs b/DiscImageChef.DiscImages/CisCopy/Write.cs new file mode 100644 index 000000000..acaa4aea0 --- /dev/null +++ b/DiscImageChef.DiscImages/CisCopy/Write.cs @@ -0,0 +1,263 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes CisCopy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class CisCopy + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + DiskType diskType; + switch(mediaType) + { + case MediaType.DOS_35_DS_DD_9: + diskType = DiskType.MF2DD; + break; + case MediaType.DOS_35_HD: + diskType = DiskType.MF2HD; + break; + case MediaType.DOS_525_DS_DD_8: + diskType = DiskType.MD2DD8; + break; + case MediaType.DOS_525_DS_DD_9: + diskType = DiskType.MD2DD; + break; + case MediaType.DOS_525_HD: + diskType = DiskType.MD2HD; + break; + case MediaType.DOS_525_SS_DD_8: + diskType = DiskType.MD1DD8; + break; + case MediaType.DOS_525_SS_DD_9: + diskType = DiskType.MD1DD; + break; + default: + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + writingStream.WriteByte((byte)diskType); + + byte tracks = 0; + + switch(diskType) + { + case DiskType.MD1DD8: + case DiskType.MD1DD: + case DiskType.MD2DD8: + case DiskType.MD2DD: + tracks = 80; + break; + case DiskType.MF2DD: + case DiskType.MD2HD: + case DiskType.MF2HD: + tracks = 160; + break; + } + + int headstep = 1; + if(diskType == DiskType.MD1DD || diskType == DiskType.MD1DD8) headstep = 2; + + for(int i = 0; i < tracks; i += headstep) + { + writingStream.WriteByte((byte)TrackType.Copied); + if(headstep == 2) writingStream.WriteByte(0); + } + + writingStream.WriteByte((byte)Compression.None); + writingOffset = writingStream.Position; + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek(writingOffset + (long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek(writingOffset + (long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD/CloneCD.cs b/DiscImageChef.DiscImages/CloneCD/CloneCD.cs new file mode 100644 index 000000000..539665ad1 --- /dev/null +++ b/DiscImageChef.DiscImages/CloneCD/CloneCD.cs @@ -0,0 +1,84 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CloneCD.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages CloneCD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: CloneCD stores subchannel deinterleaved + public partial class CloneCd : IWritableImage + { + string catalog; // TODO: Use it + IFilter ccdFilter; + byte[] cdtext; + StreamReader cueStream; + IFilter dataFilter; + Stream dataStream; + StreamWriter descriptorStream; + byte[] fulltoc; + ImageInfo imageInfo; + Dictionary<uint, ulong> offsetmap; + bool scrambled; + IFilter subFilter; + Stream subStream; + Dictionary<byte, byte> trackFlags; + string writingBaseName; + + public CloneCd() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = true, + HasSessions = true, + Version = null, + ApplicationVersion = null, + MediaTitle = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD/Constants.cs b/DiscImageChef.DiscImages/CloneCD/Constants.cs new file mode 100644 index 000000000..fe9582232 --- /dev/null +++ b/DiscImageChef.DiscImages/CloneCD/Constants.cs @@ -0,0 +1,68 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for CloneCD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class CloneCd + { + const string CCD_IDENTIFIER = @"^\s*\[CloneCD\]"; + const string DISC_IDENTIFIER = @"^\s*\[Disc\]"; + const string SESSION_IDENTIFIER = @"^\s*\[Session\s*(?<number>\d+)\]"; + const string ENTRY_IDENTIFIER = @"^\s*\[Entry\s*(?<number>\d+)\]"; + const string TRACK_IDENTIFIER = @"^\s*\[TRACK\s*(?<number>\d+)\]"; + const string CDTEXT_IDENTIFIER = @"^\s*\[CDText\]"; + const string CCD_VERSION = @"^\s*Version\s*=\s*(?<value>\d+)"; + const string DISC_ENTRIES = @"^\s*TocEntries\s*=\s*(?<value>\d+)"; + const string DISC_SESSIONS = @"^\s*Sessions\s*=\s*(?<value>\d+)"; + const string DISC_SCRAMBLED = @"^\s*DataTracksScrambled\s*=\s*(?<value>\d+)"; + const string CDTEXT_LENGTH = @"^\s*CDTextLength\s*=\s*(?<value>\d+)"; + const string DISC_CATALOG = @"^\s*CATALOG\s*=\s*(?<value>[\x21-\x7F]{13})"; + const string SESSION_PREGAP = @"^\s*PreGapMode\s*=\s*(?<value>\d+)"; + const string SESSION_SUBCHANNEL = @"^\s*PreGapSubC\s*=\s*(?<value>\d+)"; + const string ENTRY_SESSION = @"^\s*Session\s*=\s*(?<value>\d+)"; + const string ENTRY_POINT = @"^\s*Point\s*=\s*(?<value>[\w+]+)"; + const string ENTRY_ADR = @"^\s*ADR\s*=\s*(?<value>\w+)"; + const string ENTRY_CONTROL = @"^\s*Control\s*=\s*(?<value>\w+)"; + const string ENTRY_TRACKNO = @"^\s*TrackNo\s*=\s*(?<value>\d+)"; + const string ENTRY_AMIN = @"^\s*AMin\s*=\s*(?<value>\d+)"; + const string ENTRY_ASEC = @"^\s*ASec\s*=\s*(?<value>\d+)"; + const string ENTRY_AFRAME = @"^\s*AFrame\s*=\s*(?<value>\d+)"; + const string ENTRY_ALBA = @"^\s*ALBA\s*=\s*(?<value>-?\d+)"; + const string ENTRY_ZERO = @"^\s*Zero\s*=\s*(?<value>\d+)"; + const string ENTRY_PMIN = @"^\s*PMin\s*=\s*(?<value>\d+)"; + const string ENTRY_PSEC = @"^\s*PSec\s*=\s*(?<value>\d+)"; + const string ENTRY_PFRAME = @"^\s*PFrame\s*=\s*(?<value>\d+)"; + const string ENTRY_PLBA = @"^\s*PLBA\s*=\s*(?<value>\d+)"; + const string CDTEXT_ENTRIES = @"^\s*Entries\s*=\s*(?<value>\d+)"; + const string CDTEXT_ENTRY = @"^\s*Entry\s*(?<number>\d+)\s*=\s*(?<value>([0-9a-fA-F]+\s*)+)"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD/Helpers.cs b/DiscImageChef.DiscImages/CloneCD/Helpers.cs new file mode 100644 index 000000000..887628942 --- /dev/null +++ b/DiscImageChef.DiscImages/CloneCD/Helpers.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for CloneCD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class CloneCd + { + static ulong GetLba(int minute, int second, int frame) + { + return (ulong)(minute * 60 * 75 + second * 75 + frame - 150); + } + + static long MsfToLba((byte minute, byte second, byte frame) msf) + { + return msf.minute * 60 * 75 + msf.second * 75 + msf.frame - 150; + } + + static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) + { + return ((byte)((sector + 150) / 75 / 60), (byte)((sector + 150) / 75 % 60), (byte)((sector + 150) % 75)); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD/Identify.cs b/DiscImageChef.DiscImages/CloneCD/Identify.cs new file mode 100644 index 000000000..13d9d66f5 --- /dev/null +++ b/DiscImageChef.DiscImages/CloneCD/Identify.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies CloneCD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class CloneCd + { + public bool Identify(IFilter imageFilter) + { + ccdFilter = imageFilter; + + try + { + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + byte[] testArray = new byte[512]; + imageFilter.GetDataForkStream().Read(testArray, 0, 512); + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin + bool twoConsecutiveNulls = false; + for(int i = 0; i < 512; i++) + { + if(i >= imageFilter.GetDataForkStream().Length) break; + + if(testArray[i] == 0) + { + if(twoConsecutiveNulls) return false; + + twoConsecutiveNulls = true; + } + else twoConsecutiveNulls = false; + + if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) + return false; + } + + cueStream = new StreamReader(ccdFilter.GetDataForkStream()); + + string line = cueStream.ReadLine(); + + Regex hdr = new Regex(CCD_IDENTIFIER); + + Match hdm = hdr.Match(line ?? throw new InvalidOperationException()); + + return hdm.Success; + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", ccdFilter); + DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); + DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); + return false; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD/Properties.cs b/DiscImageChef.DiscImages/CloneCD/Properties.cs new file mode 100644 index 000000000..cc589cbb7 --- /dev/null +++ b/DiscImageChef.DiscImages/CloneCD/Properties.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for CloneCD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class CloneCd + { + public ImageInfo Info => imageInfo; + public string Name => "CloneCD"; + public Guid Id => new Guid("EE9C2975-2E79-427A-8EE9-F86F19165784"); + public string Format => "CloneCD"; + public List<Partition> Partitions { get; private set; } + public List<Track> Tracks { get; private set; } + public List<Session> Sessions { get; private set; } + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.CD_MCN, MediaTagType.CD_FullTOC}; + public IEnumerable<SectorTagType> SupportedSectorTags => + new[] + { + SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, + SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubHeader, + SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, SectorTagType.CdSectorSubchannel + }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, + MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, + MediaType.CDV, MediaType.DTSCD, MediaType.JaguarCD, MediaType.MEGACD, MediaType.PS1CD, MediaType.PS2CD, + MediaType.SuperCDROM2, MediaType.SVCD, MediaType.SATURNCD, MediaType.ThreeDO, MediaType.VCD, + MediaType.VCDHD, MediaType.NeoGeoCD, MediaType.PCFX + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".ccd"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD.cs b/DiscImageChef.DiscImages/CloneCD/Read.cs similarity index 67% rename from DiscImageChef.DiscImages/CloneCD.cs rename to DiscImageChef.DiscImages/CloneCD/Read.cs index f693929fa..9c9486115 100644 --- a/DiscImageChef.DiscImages/CloneCD.cs +++ b/DiscImageChef.DiscImages/CloneCD/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : CloneCD.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages CloneCD disc images. +// Reads CloneCD disc images. // // --[ License ] -------------------------------------------------------------- // @@ -34,7 +34,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using DiscImageChef.Checksums; using DiscImageChef.CommonTypes; @@ -44,147 +43,12 @@ using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Decoders.CD; -using DiscImageChef.Filters; -using Schemas; using Session = DiscImageChef.CommonTypes.Structs.Session; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // TODO: CloneCD stores subchannel deinterleaved - public class CloneCd : IWritableImage + public partial class CloneCd { - const string CCD_IDENTIFIER = @"^\s*\[CloneCD\]"; - const string DISC_IDENTIFIER = @"^\s*\[Disc\]"; - const string SESSION_IDENTIFIER = @"^\s*\[Session\s*(?<number>\d+)\]"; - const string ENTRY_IDENTIFIER = @"^\s*\[Entry\s*(?<number>\d+)\]"; - const string TRACK_IDENTIFIER = @"^\s*\[TRACK\s*(?<number>\d+)\]"; - const string CDTEXT_IDENTIFIER = @"^\s*\[CDText\]"; - const string CCD_VERSION = @"^\s*Version\s*=\s*(?<value>\d+)"; - const string DISC_ENTRIES = @"^\s*TocEntries\s*=\s*(?<value>\d+)"; - const string DISC_SESSIONS = @"^\s*Sessions\s*=\s*(?<value>\d+)"; - const string DISC_SCRAMBLED = @"^\s*DataTracksScrambled\s*=\s*(?<value>\d+)"; - const string CDTEXT_LENGTH = @"^\s*CDTextLength\s*=\s*(?<value>\d+)"; - const string DISC_CATALOG = @"^\s*CATALOG\s*=\s*(?<value>[\x21-\x7F]{13})"; - const string SESSION_PREGAP = @"^\s*PreGapMode\s*=\s*(?<value>\d+)"; - const string SESSION_SUBCHANNEL = @"^\s*PreGapSubC\s*=\s*(?<value>\d+)"; - const string ENTRY_SESSION = @"^\s*Session\s*=\s*(?<value>\d+)"; - const string ENTRY_POINT = @"^\s*Point\s*=\s*(?<value>[\w+]+)"; - const string ENTRY_ADR = @"^\s*ADR\s*=\s*(?<value>\w+)"; - const string ENTRY_CONTROL = @"^\s*Control\s*=\s*(?<value>\w+)"; - const string ENTRY_TRACKNO = @"^\s*TrackNo\s*=\s*(?<value>\d+)"; - const string ENTRY_AMIN = @"^\s*AMin\s*=\s*(?<value>\d+)"; - const string ENTRY_ASEC = @"^\s*ASec\s*=\s*(?<value>\d+)"; - const string ENTRY_AFRAME = @"^\s*AFrame\s*=\s*(?<value>\d+)"; - const string ENTRY_ALBA = @"^\s*ALBA\s*=\s*(?<value>-?\d+)"; - const string ENTRY_ZERO = @"^\s*Zero\s*=\s*(?<value>\d+)"; - const string ENTRY_PMIN = @"^\s*PMin\s*=\s*(?<value>\d+)"; - const string ENTRY_PSEC = @"^\s*PSec\s*=\s*(?<value>\d+)"; - const string ENTRY_PFRAME = @"^\s*PFrame\s*=\s*(?<value>\d+)"; - const string ENTRY_PLBA = @"^\s*PLBA\s*=\s*(?<value>\d+)"; - const string CDTEXT_ENTRIES = @"^\s*Entries\s*=\s*(?<value>\d+)"; - const string CDTEXT_ENTRY = @"^\s*Entry\s*(?<number>\d+)\s*=\s*(?<value>([0-9a-fA-F]+\s*)+)"; - string catalog; // TODO: Use it - - IFilter ccdFilter; - byte[] cdtext; - StreamReader cueStream; - IFilter dataFilter; - Stream dataStream; - StreamWriter descriptorStream; - byte[] fulltoc; - ImageInfo imageInfo; - Dictionary<uint, ulong> offsetmap; - bool scrambled; - IFilter subFilter; - Stream subStream; - Dictionary<byte, byte> trackFlags; - string writingBaseName; - - public CloneCd() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = true, - HasSessions = true, - Version = null, - ApplicationVersion = null, - MediaTitle = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "CloneCD"; - public Guid Id => new Guid("EE9C2975-2E79-427A-8EE9-F86F19165784"); - - public string Format => "CloneCD"; - - public List<Partition> Partitions { get; private set; } - - public List<Track> Tracks { get; private set; } - - public List<Session> Sessions { get; private set; } - - public bool Identify(IFilter imageFilter) - { - ccdFilter = imageFilter; - - try - { - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - byte[] testArray = new byte[512]; - imageFilter.GetDataForkStream().Read(testArray, 0, 512); - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin - bool twoConsecutiveNulls = false; - for(int i = 0; i < 512; i++) - { - if(i >= imageFilter.GetDataForkStream().Length) break; - - if(testArray[i] == 0) - { - if(twoConsecutiveNulls) return false; - - twoConsecutiveNulls = true; - } - else twoConsecutiveNulls = false; - - if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) - return false; - } - - cueStream = new StreamReader(ccdFilter.GetDataForkStream()); - - string line = cueStream.ReadLine(); - - Regex hdr = new Regex(CCD_IDENTIFIER); - - Match hdm = hdr.Match(line ?? throw new InvalidOperationException()); - - return hdm.Success; - } - catch(Exception ex) - { - DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", ccdFilter); - DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); - DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); - return false; - } - } - public bool Open(IFilter imageFilter) { if(imageFilter == null) return false; @@ -1368,604 +1232,5 @@ namespace DiscImageChef.DiscImages return failingLbas.Count <= 0; } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.CD_MCN, MediaTagType.CD_FullTOC}; - public IEnumerable<SectorTagType> SupportedSectorTags => - new[] - { - SectorTagType.CdSectorEcc, SectorTagType.CdSectorEccP, SectorTagType.CdSectorEccQ, - SectorTagType.CdSectorEdc, SectorTagType.CdSectorHeader, SectorTagType.CdSectorSubHeader, - SectorTagType.CdSectorSync, SectorTagType.CdTrackFlags, SectorTagType.CdSectorSubchannel - }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.CD, MediaType.CDDA, MediaType.CDEG, MediaType.CDG, MediaType.CDI, MediaType.CDMIDI, - MediaType.CDMRW, MediaType.CDPLUS, MediaType.CDR, MediaType.CDROM, MediaType.CDROMXA, MediaType.CDRW, - MediaType.CDV, MediaType.DTSCD, MediaType.JaguarCD, MediaType.MEGACD, MediaType.PS1CD, MediaType.PS2CD, - MediaType.SuperCDROM2, MediaType.SVCD, MediaType.SATURNCD, MediaType.ThreeDO, MediaType.VCD, - MediaType.VCDHD, MediaType.NeoGeoCD, MediaType.PCFX - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".ccd"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try - { - writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); - descriptorStream = new StreamWriter(path, false, Encoding.ASCII); - dataStream = new FileStream(writingBaseName + ".img", FileMode.OpenOrCreate, FileAccess.ReadWrite, - FileShare.None); - } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - imageInfo.MediaType = mediaType; - - trackFlags = new Dictionary<byte, byte>(); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - switch(tag) - { - case MediaTagType.CD_MCN: - catalog = Encoding.ASCII.GetString(data); - return true; - case MediaTagType.CD_FullTOC: - fulltoc = new byte[data.Length + 2]; - Array.Copy(data, 0, fulltoc, 2, data.Length); - fulltoc[0] = (byte)((data.Length & 0xFF00) >> 8); - fulltoc[1] = (byte)(data.Length & 0xFF); - return true; - default: - ErrorMessage = $"Unsupported media tag {tag}"; - return false; - } - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - // TODO: Implement ECC generation - ErrorMessage = "This format requires sectors to be raw. Generating ECC is not yet implemented"; - return false; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - // TODO: Implement ECC generation - ErrorMessage = "This format requires sectors to be raw. Generating ECC is not yet implemented"; - return false; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(data.Length != track.TrackRawBytesPerSector) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - dataStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - dataStream.Write(data, 0, data.Length); - - return true; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - if(data.Length % track.TrackRawBytesPerSector != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - dataStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), - SeekOrigin.Begin); - dataStream.Write(data, 0, data.Length); - - return true; - } - - public bool SetTracks(List<Track> tracks) - { - ulong currentDataOffset = 0; - ulong currentSubchannelOffset = 0; - - Tracks = new List<Track>(); - foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) - { - Track newTrack = track; - uint subchannelSize; - switch(track.TrackSubchannelType) - { - case TrackSubchannelType.None: - subchannelSize = 0; - break; - case TrackSubchannelType.Raw: - case TrackSubchannelType.RawInterleaved: - subchannelSize = 96; - break; - default: - ErrorMessage = $"Unsupported subchannel type {track.TrackSubchannelType}"; - return false; - } - - newTrack.TrackFileOffset = currentDataOffset; - newTrack.TrackSubchannelOffset = currentSubchannelOffset; - - currentDataOffset += (ulong)newTrack.TrackRawBytesPerSector * - (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); - currentSubchannelOffset += subchannelSize * (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); - - Tracks.Add(newTrack); - } - - return true; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - dataStream.Flush(); - dataStream.Close(); - - subStream?.Flush(); - subStream?.Close(); - - FullTOC.CDFullTOC? nullableToc = null; - FullTOC.CDFullTOC toc; - - // Easy, just decode the real toc - if(fulltoc != null) nullableToc = FullTOC.Decode(fulltoc); - - // Not easy, create a toc from scratch - if(nullableToc == null) - { - toc = new FullTOC.CDFullTOC(); - Dictionary<byte, byte> sessionEndingTrack = new Dictionary<byte, byte>(); - toc.FirstCompleteSession = byte.MaxValue; - toc.LastCompleteSession = byte.MinValue; - List<FullTOC.TrackDataDescriptor> trackDescriptors = new List<FullTOC.TrackDataDescriptor>(); - byte currentTrack = 0; - - foreach(Track track in Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence)) - { - if(track.TrackSession < toc.FirstCompleteSession) - toc.FirstCompleteSession = (byte)track.TrackSession; - - if(track.TrackSession <= toc.LastCompleteSession) - { - currentTrack = (byte)track.TrackSequence; - continue; - } - - if(toc.LastCompleteSession > 0) sessionEndingTrack.Add(toc.LastCompleteSession, currentTrack); - - toc.LastCompleteSession = (byte)track.TrackSession; - } - - byte currentSession = 0; - - foreach(Track track in Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence)) - { - trackFlags.TryGetValue((byte)track.TrackSequence, out byte trackControl); - - if(trackControl == 0 && track.TrackType != TrackType.Audio) trackControl = (byte)CdFlags.DataTrack; - - // Lead-Out - if(track.TrackSession > currentSession && currentSession != 0) - { - (byte minute, byte second, byte frame) leadoutAmsf = LbaToMsf(track.TrackStartSector - 150); - (byte minute, byte second, byte frame) leadoutPmsf = - LbaToMsf(Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).Last() - .TrackStartSector); - - // Lead-out - trackDescriptors.Add(new FullTOC.TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xB0, - ADR = 5, - CONTROL = 0, - HOUR = 0, - Min = leadoutAmsf.minute, - Sec = leadoutAmsf.second, - Frame = leadoutAmsf.frame, - PHOUR = 2, - PMIN = leadoutPmsf.minute, - PSEC = leadoutPmsf.second, - PFRAME = leadoutPmsf.frame - }); - - // This seems to be constant? It should not exist on CD-ROM but CloneCD creates them anyway - // Format seems like ATIP, but ATIP should not be as 0xC0 in TOC... - trackDescriptors.Add(new FullTOC.TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xC0, - ADR = 5, - CONTROL = 0, - Min = 128, - PMIN = 97, - PSEC = 25 - }); - } - - // Lead-in - if(track.TrackSession > currentSession) - { - currentSession = (byte)track.TrackSession; - sessionEndingTrack.TryGetValue(currentSession, out byte endingTrackNumber); - (byte minute, byte second, byte frame) leadinPmsf = - LbaToMsf(Tracks.FirstOrDefault(t => t.TrackSequence == endingTrackNumber).TrackEndSector + - 1); - - // Starting track - trackDescriptors.Add(new FullTOC.TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xA0, - ADR = 1, - CONTROL = trackControl, - PMIN = (byte)track.TrackSequence - }); - - // Ending track - trackDescriptors.Add(new FullTOC.TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xA1, - ADR = 1, - CONTROL = trackControl, - PMIN = endingTrackNumber - }); - - // Lead-out start - trackDescriptors.Add(new FullTOC.TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xA2, - ADR = 1, - CONTROL = trackControl, - PHOUR = 0, - PMIN = leadinPmsf.minute, - PSEC = leadinPmsf.second, - PFRAME = leadinPmsf.frame - }); - } - - (byte minute, byte second, byte frame) pmsf = LbaToMsf(track.TrackStartSector); - - // Track - trackDescriptors.Add(new FullTOC.TrackDataDescriptor - { - SessionNumber = (byte)track.TrackSession, - POINT = (byte)track.TrackSequence, - ADR = 1, - CONTROL = trackControl, - PHOUR = 0, - PMIN = pmsf.minute, - PSEC = pmsf.second, - PFRAME = pmsf.frame - }); - } - - toc.TrackDescriptors = trackDescriptors.ToArray(); - } - else toc = nullableToc.Value; - - descriptorStream.WriteLine("[CloneCD]"); - descriptorStream.WriteLine("Version=2"); - descriptorStream.WriteLine("[Disc]"); - descriptorStream.WriteLine("TocEntries={0}", toc.TrackDescriptors.Length); - descriptorStream.WriteLine("Sessions={0}", toc.LastCompleteSession); - descriptorStream.WriteLine("DataTracksScrambled=0"); - descriptorStream.WriteLine("CDTextLength=0"); - if(!string.IsNullOrEmpty(catalog)) descriptorStream.WriteLine("CATALOG={0}", catalog); - for(int i = 1; i <= toc.LastCompleteSession; i++) - { - // TODO: Use first track of session info - descriptorStream.WriteLine("[Session {0}]", i); - descriptorStream.WriteLine("PreGapMode=0"); - descriptorStream.WriteLine("PreGapSubC=0"); - } - - for(int i = 0; i < toc.TrackDescriptors.Length; i++) - { - long alba = MsfToLba((toc.TrackDescriptors[i].Min, toc.TrackDescriptors[i].Sec, - toc.TrackDescriptors[i].Frame)); - long plba = MsfToLba((toc.TrackDescriptors[i].PMIN, toc.TrackDescriptors[i].PSEC, - toc.TrackDescriptors[i].PFRAME)); - - if(alba > 405000) alba = (alba - 405000 + 300) * -1; - if(plba > 405000) plba = (plba - 405000 + 300) * -1; - - descriptorStream.WriteLine("[Entry {0}]", i); - descriptorStream.WriteLine("Session={0}", toc.TrackDescriptors[i].SessionNumber); - descriptorStream.WriteLine("Point=0x{0:x2}", toc.TrackDescriptors[i].POINT); - descriptorStream.WriteLine("ADR=0x{0:x2}", toc.TrackDescriptors[i].ADR); - descriptorStream.WriteLine("Control=0x{0:x2}", toc.TrackDescriptors[i].CONTROL); - descriptorStream.WriteLine("TrackNo={0}", toc.TrackDescriptors[i].TNO); - descriptorStream.WriteLine("AMin={0}", toc.TrackDescriptors[i].Min); - descriptorStream.WriteLine("ASec={0}", toc.TrackDescriptors[i].Sec); - descriptorStream.WriteLine("AFrame={0}", toc.TrackDescriptors[i].Frame); - descriptorStream.WriteLine("ALBA={0}", alba); - descriptorStream.WriteLine("Zero={0}", - ((toc.TrackDescriptors[i].HOUR & 0x0F) << 4) + - (toc.TrackDescriptors[i].PHOUR & 0x0F)); - descriptorStream.WriteLine("PMin={0}", toc.TrackDescriptors[i].PMIN); - descriptorStream.WriteLine("PSec={0}", toc.TrackDescriptors[i].PSEC); - descriptorStream.WriteLine("PFrame={0}", toc.TrackDescriptors[i].PFRAME); - descriptorStream.WriteLine("PLBA={0}", plba); - } - - descriptorStream.Flush(); - descriptorStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - { - if(data.Length != 1) - { - ErrorMessage = "Incorrect data size for track flags"; - return false; - } - - trackFlags.Add((byte)track.TrackSequence, data[0]); - - return true; - } - case SectorTagType.CdSectorSubchannel: - { - if(track.TrackSubchannelType == 0) - { - ErrorMessage = - $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; - return false; - } - - if(data.Length != 96) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - if(subStream == null) - try - { - subStream = new FileStream(writingBaseName + ".sub", FileMode.OpenOrCreate, - FileAccess.ReadWrite, FileShare.None); - } - catch(IOException e) - { - ErrorMessage = $"Could not create subchannel file, exception {e.Message}"; - return false; - } - - subStream.Seek((long)(track.TrackSubchannelOffset + (sectorAddress - track.TrackStartSector) * 96), - SeekOrigin.Begin); - subStream.Write(data, 0, data.Length); - - return true; - } - default: - ErrorMessage = $"Unsupported tag type {tag}"; - return false; - } - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Track track = - Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); - case SectorTagType.CdSectorSubchannel: - { - if(track.TrackSubchannelType == 0) - { - ErrorMessage = - $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; - return false; - } - - if(data.Length % 96 != 0) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - if(subStream == null) - try - { - subStream = new FileStream(writingBaseName + ".sub", FileMode.OpenOrCreate, - FileAccess.ReadWrite, FileShare.None); - } - catch(IOException e) - { - ErrorMessage = $"Could not create subchannel file, exception {e.Message}"; - return false; - } - - subStream.Seek((long)(track.TrackSubchannelOffset + (sectorAddress - track.TrackStartSector) * 96), - SeekOrigin.Begin); - subStream.Write(data, 0, data.Length); - - return true; - } - default: - ErrorMessage = $"Unsupported tag type {tag}"; - return false; - } - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - static ulong GetLba(int minute, int second, int frame) - { - return (ulong)(minute * 60 * 75 + second * 75 + frame - 150); - } - - static long MsfToLba((byte minute, byte second, byte frame) msf) - { - return msf.minute * 60 * 75 + msf.second * 75 + msf.frame - 150; - } - - static (byte minute, byte second, byte frame) LbaToMsf(ulong sector) - { - return ((byte)((sector + 150) / 75 / 60), (byte)((sector + 150) / 75 % 60), (byte)((sector + 150) % 75)); - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD/Unsupported.cs b/DiscImageChef.DiscImages/CloneCD/Unsupported.cs new file mode 100644 index 000000000..97c5cf361 --- /dev/null +++ b/DiscImageChef.DiscImages/CloneCD/Unsupported.cs @@ -0,0 +1,42 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by CloneCD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class CloneCd + { + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CloneCD/Write.cs b/DiscImageChef.DiscImages/CloneCD/Write.cs new file mode 100644 index 000000000..069ffa447 --- /dev/null +++ b/DiscImageChef.DiscImages/CloneCD/Write.cs @@ -0,0 +1,602 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes CloneCD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Decoders.CD; +using Schemas; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + public partial class CloneCd + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try + { + writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); + descriptorStream = new StreamWriter(path, false, Encoding.ASCII); + dataStream = new FileStream(writingBaseName + ".img", FileMode.OpenOrCreate, FileAccess.ReadWrite, + FileShare.None); + } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + imageInfo.MediaType = mediaType; + + trackFlags = new Dictionary<byte, byte>(); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + switch(tag) + { + case MediaTagType.CD_MCN: + catalog = Encoding.ASCII.GetString(data); + return true; + case MediaTagType.CD_FullTOC: + fulltoc = new byte[data.Length + 2]; + Array.Copy(data, 0, fulltoc, 2, data.Length); + fulltoc[0] = (byte)((data.Length & 0xFF00) >> 8); + fulltoc[1] = (byte)(data.Length & 0xFF); + return true; + default: + ErrorMessage = $"Unsupported media tag {tag}"; + return false; + } + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + // TODO: Implement ECC generation + ErrorMessage = "This format requires sectors to be raw. Generating ECC is not yet implemented"; + return false; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + // TODO: Implement ECC generation + ErrorMessage = "This format requires sectors to be raw. Generating ECC is not yet implemented"; + return false; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(data.Length != track.TrackRawBytesPerSector) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + dataStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + dataStream.Write(data, 0, data.Length); + + return true; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + if(data.Length % track.TrackRawBytesPerSector != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + dataStream.Seek((long)(track.TrackFileOffset + (sectorAddress - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector), + SeekOrigin.Begin); + dataStream.Write(data, 0, data.Length); + + return true; + } + + public bool SetTracks(List<Track> tracks) + { + ulong currentDataOffset = 0; + ulong currentSubchannelOffset = 0; + + Tracks = new List<Track>(); + foreach(Track track in tracks.OrderBy(t => t.TrackSequence)) + { + Track newTrack = track; + uint subchannelSize; + switch(track.TrackSubchannelType) + { + case TrackSubchannelType.None: + subchannelSize = 0; + break; + case TrackSubchannelType.Raw: + case TrackSubchannelType.RawInterleaved: + subchannelSize = 96; + break; + default: + ErrorMessage = $"Unsupported subchannel type {track.TrackSubchannelType}"; + return false; + } + + newTrack.TrackFileOffset = currentDataOffset; + newTrack.TrackSubchannelOffset = currentSubchannelOffset; + + currentDataOffset += (ulong)newTrack.TrackRawBytesPerSector * + (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); + currentSubchannelOffset += subchannelSize * (newTrack.TrackEndSector - newTrack.TrackStartSector + 1); + + Tracks.Add(newTrack); + } + + return true; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + dataStream.Flush(); + dataStream.Close(); + + subStream?.Flush(); + subStream?.Close(); + + FullTOC.CDFullTOC? nullableToc = null; + FullTOC.CDFullTOC toc; + + // Easy, just decode the real toc + if(fulltoc != null) nullableToc = FullTOC.Decode(fulltoc); + + // Not easy, create a toc from scratch + if(nullableToc == null) + { + toc = new FullTOC.CDFullTOC(); + Dictionary<byte, byte> sessionEndingTrack = new Dictionary<byte, byte>(); + toc.FirstCompleteSession = byte.MaxValue; + toc.LastCompleteSession = byte.MinValue; + List<FullTOC.TrackDataDescriptor> trackDescriptors = new List<FullTOC.TrackDataDescriptor>(); + byte currentTrack = 0; + + foreach(Track track in Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence)) + { + if(track.TrackSession < toc.FirstCompleteSession) + toc.FirstCompleteSession = (byte)track.TrackSession; + + if(track.TrackSession <= toc.LastCompleteSession) + { + currentTrack = (byte)track.TrackSequence; + continue; + } + + if(toc.LastCompleteSession > 0) sessionEndingTrack.Add(toc.LastCompleteSession, currentTrack); + + toc.LastCompleteSession = (byte)track.TrackSession; + } + + byte currentSession = 0; + + foreach(Track track in Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence)) + { + trackFlags.TryGetValue((byte)track.TrackSequence, out byte trackControl); + + if(trackControl == 0 && track.TrackType != TrackType.Audio) trackControl = (byte)CdFlags.DataTrack; + + // Lead-Out + if(track.TrackSession > currentSession && currentSession != 0) + { + (byte minute, byte second, byte frame) leadoutAmsf = LbaToMsf(track.TrackStartSector - 150); + (byte minute, byte second, byte frame) leadoutPmsf = + LbaToMsf(Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).Last() + .TrackStartSector); + + // Lead-out + trackDescriptors.Add(new FullTOC.TrackDataDescriptor + { + SessionNumber = currentSession, + POINT = 0xB0, + ADR = 5, + CONTROL = 0, + HOUR = 0, + Min = leadoutAmsf.minute, + Sec = leadoutAmsf.second, + Frame = leadoutAmsf.frame, + PHOUR = 2, + PMIN = leadoutPmsf.minute, + PSEC = leadoutPmsf.second, + PFRAME = leadoutPmsf.frame + }); + + // This seems to be constant? It should not exist on CD-ROM but CloneCD creates them anyway + // Format seems like ATIP, but ATIP should not be as 0xC0 in TOC... + trackDescriptors.Add(new FullTOC.TrackDataDescriptor + { + SessionNumber = currentSession, + POINT = 0xC0, + ADR = 5, + CONTROL = 0, + Min = 128, + PMIN = 97, + PSEC = 25 + }); + } + + // Lead-in + if(track.TrackSession > currentSession) + { + currentSession = (byte)track.TrackSession; + sessionEndingTrack.TryGetValue(currentSession, out byte endingTrackNumber); + (byte minute, byte second, byte frame) leadinPmsf = + LbaToMsf(Tracks.FirstOrDefault(t => t.TrackSequence == endingTrackNumber).TrackEndSector + + 1); + + // Starting track + trackDescriptors.Add(new FullTOC.TrackDataDescriptor + { + SessionNumber = currentSession, + POINT = 0xA0, + ADR = 1, + CONTROL = trackControl, + PMIN = (byte)track.TrackSequence + }); + + // Ending track + trackDescriptors.Add(new FullTOC.TrackDataDescriptor + { + SessionNumber = currentSession, + POINT = 0xA1, + ADR = 1, + CONTROL = trackControl, + PMIN = endingTrackNumber + }); + + // Lead-out start + trackDescriptors.Add(new FullTOC.TrackDataDescriptor + { + SessionNumber = currentSession, + POINT = 0xA2, + ADR = 1, + CONTROL = trackControl, + PHOUR = 0, + PMIN = leadinPmsf.minute, + PSEC = leadinPmsf.second, + PFRAME = leadinPmsf.frame + }); + } + + (byte minute, byte second, byte frame) pmsf = LbaToMsf(track.TrackStartSector); + + // Track + trackDescriptors.Add(new FullTOC.TrackDataDescriptor + { + SessionNumber = (byte)track.TrackSession, + POINT = (byte)track.TrackSequence, + ADR = 1, + CONTROL = trackControl, + PHOUR = 0, + PMIN = pmsf.minute, + PSEC = pmsf.second, + PFRAME = pmsf.frame + }); + } + + toc.TrackDescriptors = trackDescriptors.ToArray(); + } + else toc = nullableToc.Value; + + descriptorStream.WriteLine("[CloneCD]"); + descriptorStream.WriteLine("Version=2"); + descriptorStream.WriteLine("[Disc]"); + descriptorStream.WriteLine("TocEntries={0}", toc.TrackDescriptors.Length); + descriptorStream.WriteLine("Sessions={0}", toc.LastCompleteSession); + descriptorStream.WriteLine("DataTracksScrambled=0"); + descriptorStream.WriteLine("CDTextLength=0"); + if(!string.IsNullOrEmpty(catalog)) descriptorStream.WriteLine("CATALOG={0}", catalog); + for(int i = 1; i <= toc.LastCompleteSession; i++) + { + // TODO: Use first track of session info + descriptorStream.WriteLine("[Session {0}]", i); + descriptorStream.WriteLine("PreGapMode=0"); + descriptorStream.WriteLine("PreGapSubC=0"); + } + + for(int i = 0; i < toc.TrackDescriptors.Length; i++) + { + long alba = MsfToLba((toc.TrackDescriptors[i].Min, toc.TrackDescriptors[i].Sec, + toc.TrackDescriptors[i].Frame)); + long plba = MsfToLba((toc.TrackDescriptors[i].PMIN, toc.TrackDescriptors[i].PSEC, + toc.TrackDescriptors[i].PFRAME)); + + if(alba > 405000) alba = (alba - 405000 + 300) * -1; + if(plba > 405000) plba = (plba - 405000 + 300) * -1; + + descriptorStream.WriteLine("[Entry {0}]", i); + descriptorStream.WriteLine("Session={0}", toc.TrackDescriptors[i].SessionNumber); + descriptorStream.WriteLine("Point=0x{0:x2}", toc.TrackDescriptors[i].POINT); + descriptorStream.WriteLine("ADR=0x{0:x2}", toc.TrackDescriptors[i].ADR); + descriptorStream.WriteLine("Control=0x{0:x2}", toc.TrackDescriptors[i].CONTROL); + descriptorStream.WriteLine("TrackNo={0}", toc.TrackDescriptors[i].TNO); + descriptorStream.WriteLine("AMin={0}", toc.TrackDescriptors[i].Min); + descriptorStream.WriteLine("ASec={0}", toc.TrackDescriptors[i].Sec); + descriptorStream.WriteLine("AFrame={0}", toc.TrackDescriptors[i].Frame); + descriptorStream.WriteLine("ALBA={0}", alba); + descriptorStream.WriteLine("Zero={0}", + ((toc.TrackDescriptors[i].HOUR & 0x0F) << 4) + + (toc.TrackDescriptors[i].PHOUR & 0x0F)); + descriptorStream.WriteLine("PMin={0}", toc.TrackDescriptors[i].PMIN); + descriptorStream.WriteLine("PSec={0}", toc.TrackDescriptors[i].PSEC); + descriptorStream.WriteLine("PFrame={0}", toc.TrackDescriptors[i].PFRAME); + descriptorStream.WriteLine("PLBA={0}", plba); + } + + descriptorStream.Flush(); + descriptorStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + { + if(data.Length != 1) + { + ErrorMessage = "Incorrect data size for track flags"; + return false; + } + + trackFlags.Add((byte)track.TrackSequence, data[0]); + + return true; + } + case SectorTagType.CdSectorSubchannel: + { + if(track.TrackSubchannelType == 0) + { + ErrorMessage = + $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; + return false; + } + + if(data.Length != 96) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + if(subStream == null) + try + { + subStream = new FileStream(writingBaseName + ".sub", FileMode.OpenOrCreate, + FileAccess.ReadWrite, FileShare.None); + } + catch(IOException e) + { + ErrorMessage = $"Could not create subchannel file, exception {e.Message}"; + return false; + } + + subStream.Seek((long)(track.TrackSubchannelOffset + (sectorAddress - track.TrackStartSector) * 96), + SeekOrigin.Begin); + subStream.Write(data, 0, data.Length); + + return true; + } + default: + ErrorMessage = $"Unsupported tag type {tag}"; + return false; + } + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Track track = + Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); + case SectorTagType.CdSectorSubchannel: + { + if(track.TrackSubchannelType == 0) + { + ErrorMessage = + $"Trying to write subchannel to track {track.TrackSequence}, that does not have subchannel"; + return false; + } + + if(data.Length % 96 != 0) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + if(subStream == null) + try + { + subStream = new FileStream(writingBaseName + ".sub", FileMode.OpenOrCreate, + FileAccess.ReadWrite, FileShare.None); + } + catch(IOException e) + { + ErrorMessage = $"Could not create subchannel file, exception {e.Message}"; + return false; + } + + subStream.Seek((long)(track.TrackSubchannelOffset + (sectorAddress - track.TrackStartSector) * 96), + SeekOrigin.Begin); + subStream.Write(data, 0, data.Length); + + return true; + } + default: + ErrorMessage = $"Unsupported tag type {tag}"; + return false; + } + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM.cs b/DiscImageChef.DiscImages/CopyQM.cs deleted file mode 100644 index 1a8b14fb6..000000000 --- a/DiscImageChef.DiscImages/CopyQM.cs +++ /dev/null @@ -1,501 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : CopyQM.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Sydex CopyQM disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class CopyQm : IMediaImage - { - const ushort COPYQM_MAGIC = 0x5143; - const byte COPYQM_MARK = 0x14; - - const byte COPYQM_FAT = 0; - const byte COPYQM_BLIND = 1; - const byte COPYQM_HFS = 2; - - const byte COPYQM_525_DD = 1; - const byte COPYQM_525_HD = 2; - const byte COPYQM_35_DD = 3; - const byte COPYQM_35_HD = 4; - const byte COPYQM_35_ED = 6; - - readonly uint[] copyQmCrcTable = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, - 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, - 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, - 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, - 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, - 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, - 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, - 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, - 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, - 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, - 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, - 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, - 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, - 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, - 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, - 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, - 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, - 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, - 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, - 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, - 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, - 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, - 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, - 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, - 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, - 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - uint calculatedDataCrc; - byte[] decodedDisk; - MemoryStream decodedImage; - - CopyQmHeader header; - - bool headerChecksumOk; - ImageInfo imageInfo; - - public CopyQm() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Sydex CopyQM"; - public Guid Id => new Guid("147E927D-3A92-4E0C-82CD-142F5A4FA76D"); - - public string Format => "Sydex CopyQM"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < 133) return false; - - byte[] hdr = new byte[133]; - stream.Read(hdr, 0, 133); - - ushort magic = BitConverter.ToUInt16(hdr, 0); - - return magic == COPYQM_MAGIC && hdr[0x02] == COPYQM_MARK && 133 + hdr[0x6F] < stream.Length; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - byte[] hdr = new byte[133]; - - stream.Read(hdr, 0, 133); - header = new CopyQmHeader(); - IntPtr hdrPtr = Marshal.AllocHGlobal(133); - Marshal.Copy(hdr, 0, hdrPtr, 133); - header = (CopyQmHeader)Marshal.PtrToStructure(hdrPtr, typeof(CopyQmHeader)); - Marshal.FreeHGlobal(hdrPtr); - - DicConsole.DebugWriteLine("CopyQM plugin", "header.magic = 0x{0:X4}", header.magic); - DicConsole.DebugWriteLine("CopyQM plugin", "header.mark = 0x{0:X2}", header.mark); - DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorSize = {0}", header.sectorSize); - DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorPerCluster = {0}", header.sectorPerCluster); - DicConsole.DebugWriteLine("CopyQM plugin", "header.reservedSectors = {0}", header.reservedSectors); - DicConsole.DebugWriteLine("CopyQM plugin", "header.fatCopy = {0}", header.fatCopy); - DicConsole.DebugWriteLine("CopyQM plugin", "header.rootEntries = {0}", header.rootEntries); - DicConsole.DebugWriteLine("CopyQM plugin", "header.sectors = {0}", header.sectors); - DicConsole.DebugWriteLine("CopyQM plugin", "header.mediaType = 0x{0:X2}", header.mediaType); - DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerFat = {0}", header.sectorsPerFat); - DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerTrack = {0}", header.sectorsPerTrack); - DicConsole.DebugWriteLine("CopyQM plugin", "header.heads = {0}", header.heads); - DicConsole.DebugWriteLine("CopyQM plugin", "header.hidden = {0}", header.hidden); - DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsBig = {0}", header.sectorsBig); - DicConsole.DebugWriteLine("CopyQM plugin", "header.description = {0}", header.description); - DicConsole.DebugWriteLine("CopyQM plugin", "header.blind = {0}", header.blind); - DicConsole.DebugWriteLine("CopyQM plugin", "header.density = {0}", header.density); - DicConsole.DebugWriteLine("CopyQM plugin", "header.imageCylinders = {0}", header.imageCylinders); - DicConsole.DebugWriteLine("CopyQM plugin", "header.totalCylinders = {0}", header.totalCylinders); - DicConsole.DebugWriteLine("CopyQM plugin", "header.crc = 0x{0:X8}", header.crc); - DicConsole.DebugWriteLine("CopyQM plugin", "header.volumeLabel = {0}", header.volumeLabel); - DicConsole.DebugWriteLine("CopyQM plugin", "header.time = 0x{0:X4}", header.time); - DicConsole.DebugWriteLine("CopyQM plugin", "header.date = 0x{0:X4}", header.date); - DicConsole.DebugWriteLine("CopyQM plugin", "header.commentLength = {0}", header.commentLength); - DicConsole.DebugWriteLine("CopyQM plugin", "header.secbs = {0}", header.secbs); - DicConsole.DebugWriteLine("CopyQM plugin", "header.unknown = 0x{0:X4}", header.unknown); - DicConsole.DebugWriteLine("CopyQM plugin", "header.interleave = {0}", header.interleave); - DicConsole.DebugWriteLine("CopyQM plugin", "header.skew = {0}", header.skew); - DicConsole.DebugWriteLine("CopyQM plugin", "header.drive = {0}", header.drive); - - byte[] cmt = new byte[header.commentLength]; - stream.Read(cmt, 0, header.commentLength); - imageInfo.Comments = StringHandlers.CToString(cmt); - decodedImage = new MemoryStream(); - - calculatedDataCrc = 0; - - while(stream.Position + 2 < stream.Length) - { - byte[] runLengthBytes = new byte[2]; - if(stream.Read(runLengthBytes, 0, 2) != 2) break; - - short runLength = BitConverter.ToInt16(runLengthBytes, 0); - - if(runLength < 0) - { - byte repeatedByte = (byte)stream.ReadByte(); - byte[] repeatedArray = new byte[runLength * -1]; - ArrayHelpers.ArrayFill(repeatedArray, repeatedByte); - - for(int i = 0; i < runLength * -1; i++) - { - decodedImage.WriteByte(repeatedByte); - calculatedDataCrc = copyQmCrcTable[(repeatedByte ^ calculatedDataCrc) & 0x3F] ^ - (calculatedDataCrc >> 8); - } - } - else if(runLength > 0) - { - byte[] nonRepeated = new byte[runLength]; - stream.Read(nonRepeated, 0, runLength); - decodedImage.Write(nonRepeated, 0, runLength); - - foreach(byte c in nonRepeated) - calculatedDataCrc = copyQmCrcTable[(c ^ calculatedDataCrc) & 0x3F] ^ (calculatedDataCrc >> 8); - } - } - - // In case there is omitted data - long sectors = header.sectorsPerTrack * header.heads * header.totalCylinders; - - long fillingLen = sectors * header.sectorSize - decodedImage.Length; - - if(fillingLen > 0) - { - byte[] filling = new byte[fillingLen]; - ArrayHelpers.ArrayFill(filling, (byte)0xF6); - decodedImage.Write(filling, 0, filling.Length); - } - - int sum = 0; - for(int i = 0; i < hdr.Length - 1; i++) sum += hdr[i]; - - headerChecksumOk = ((-1 * sum) & 0xFF) == header.headerChecksum; - - DicConsole.DebugWriteLine("CopyQM plugin", "Calculated header checksum = 0x{0:X2}, {1}", (-1 * sum) & 0xFF, - headerChecksumOk); - DicConsole.DebugWriteLine("CopyQM plugin", "Calculated data CRC = 0x{0:X8}, {1}", calculatedDataCrc, - calculatedDataCrc == header.crc); - - imageInfo.Application = "CopyQM"; - imageInfo.CreationTime = DateHandlers.DosToDateTime(header.date, header.time); - imageInfo.LastModificationTime = imageInfo.CreationTime; - imageInfo.MediaTitle = header.volumeLabel; - imageInfo.ImageSize = (ulong)(stream.Length - 133 - header.commentLength); - imageInfo.Sectors = (ulong)sectors; - imageInfo.SectorSize = header.sectorSize; - - imageInfo.MediaType = Geometry.GetMediaType(((ushort)header.totalCylinders, (byte)header.heads, - header.sectorsPerTrack, (uint)header.sectorSize, - MediaEncoding.MFM, false)); - - switch(imageInfo.MediaType) - { - case MediaType.NEC_525_HD when header.drive == COPYQM_35_HD || header.drive == COPYQM_35_ED: - imageInfo.MediaType = MediaType.NEC_35_HD_8; - break; - case MediaType.DOS_525_HD when header.drive == COPYQM_35_HD || header.drive == COPYQM_35_ED: - imageInfo.MediaType = MediaType.NEC_35_HD_15; - break; - case MediaType.RX50 when header.drive == COPYQM_525_DD || header.drive == COPYQM_525_HD: - imageInfo.MediaType = MediaType.ATARI_35_SS_DD; - break; - } - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - decodedDisk = decodedImage.ToArray(); - - decodedImage.Close(); - - DicConsole.VerboseWriteLine("CopyQM image contains a disk of type {0}", imageInfo.MediaType); - if(!string.IsNullOrEmpty(imageInfo.Comments)) - DicConsole.VerboseWriteLine("CopyQM comments: {0}", imageInfo.Comments); - - imageInfo.Heads = header.heads; - imageInfo.Cylinders = header.totalCylinders; - imageInfo.SectorsPerTrack = header.sectorsPerTrack; - - return true; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return calculatedDataCrc == header.crc && headerChecksumOk; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Array.Copy(decodedDisk, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, - length * imageInfo.SectorSize); - - return buffer; - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CopyQmHeader - { - /// <summary>0x00 magic, "CQ"</summary> - public ushort magic; - /// <summary>0x02 always 0x14</summary> - public byte mark; - /// <summary>0x03 Bytes per sector (part of FAT's BPB)</summary> - public ushort sectorSize; - /// <summary>0x05 Sectors per cluster (part of FAT's BPB)</summary> - public byte sectorPerCluster; - /// <summary>0x06 Reserved sectors (part of FAT's BPB)</summary> - public ushort reservedSectors; - /// <summary>0x08 Number of FAT copies (part of FAT's BPB)</summary> - public byte fatCopy; - /// <summary>0x09 Maximum number of entries in root directory (part of FAT's BPB)</summary> - public ushort rootEntries; - /// <summary>0x0B Sectors on disk (part of FAT's BPB)</summary> - public ushort sectors; - /// <summary>0x0D Media descriptor (part of FAT's BPB)</summary> - public byte mediaType; - /// <summary>0x0E Sectors per FAT (part of FAT's BPB)</summary> - public ushort sectorsPerFat; - /// <summary>0x10 Sectors per track (part of FAT's BPB)</summary> - public ushort sectorsPerTrack; - /// <summary>0x12 Heads (part of FAT's BPB)</summary> - public ushort heads; - /// <summary>0x14 Hidden sectors (part of FAT's BPB)</summary> - public uint hidden; - /// <summary>0x18 Sectors on disk (part of FAT's BPB)</summary> - public uint sectorsBig; - /// <summary>0x1C Description</summary> - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)] - public string description; - /// <summary>0x58 Blind mode. 0 = DOS, 1 = blind, 2 = HFS</summary> - public byte blind; - /// <summary>0x59 Density. 0 = Double, 1 = High, 2 = Quad/Extra</summary> - public byte density; - /// <summary>0x5A Cylinders in image</summary> - public byte imageCylinders; - /// <summary>0x5B Cylinders on disk</summary> - public byte totalCylinders; - /// <summary>0x5C CRC32 of data</summary> - public uint crc; - /// <summary>0x60 DOS volume label</summary> - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] - public string volumeLabel; - /// <summary>0x6B Modification time</summary> - public ushort time; - /// <summary>0x6D Modification date</summary> - public ushort date; - /// <summary>0x6F Comment length</summary> - public ushort commentLength; - /// <summary>0x71 Sector base (first sector - 1)</summary> - public byte secbs; - /// <summary>0x72 Unknown</summary> - public ushort unknown; - /// <summary>0x74 Interleave</summary> - public byte interleave; - /// <summary>0x75 Skew</summary> - public byte skew; - /// <summary>0x76 Source drive type. 1 = 5.25" DD, 2 = 5.25" HD, 3 = 3.5" DD, 4 = 3.5" HD, 6 = 3.5" ED</summary> - public byte drive; - /// <summary>0x77 Filling bytes</summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - public byte[] fill; - /// <summary>0x84 Header checksum</summary> - public byte headerChecksum; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM/Constants.cs b/DiscImageChef.DiscImages/CopyQM/Constants.cs new file mode 100644 index 000000000..99045f293 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM/Constants.cs @@ -0,0 +1,81 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Sydex CopyQM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class CopyQm + { + const ushort COPYQM_MAGIC = 0x5143; + const byte COPYQM_MARK = 0x14; + const byte COPYQM_FAT = 0; + const byte COPYQM_BLIND = 1; + const byte COPYQM_HFS = 2; + const byte COPYQM_525_DD = 1; + const byte COPYQM_525_HD = 2; + const byte COPYQM_35_DD = 3; + const byte COPYQM_35_HD = 4; + const byte COPYQM_35_ED = 6; + + readonly uint[] copyQmCrcTable = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, + 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, + 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, + 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, + 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, + 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, + 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, + 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, + 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, + 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, + 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, + 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, + 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM/CopyQM.cs b/DiscImageChef.DiscImages/CopyQM/CopyQM.cs new file mode 100644 index 000000000..e10f4fbc5 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM/CopyQM.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CopyQM.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Sydex CopyQM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class CopyQm : IMediaImage + { + uint calculatedDataCrc; + byte[] decodedDisk; + MemoryStream decodedImage; + CopyQmHeader header; + bool headerChecksumOk; + ImageInfo imageInfo; + + public CopyQm() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM/Identify.cs b/DiscImageChef.DiscImages/CopyQM/Identify.cs new file mode 100644 index 000000000..63f6cda87 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM/Identify.cs @@ -0,0 +1,55 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Sydex CopyQM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class CopyQm + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < 133) return false; + + byte[] hdr = new byte[133]; + stream.Read(hdr, 0, 133); + + ushort magic = BitConverter.ToUInt16(hdr, 0); + + return magic == COPYQM_MAGIC && hdr[0x02] == COPYQM_MARK && 133 + hdr[0x6F] < stream.Length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM/Properties.cs b/DiscImageChef.DiscImages/CopyQM/Properties.cs new file mode 100644 index 000000000..b2b9e9861 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM/Properties.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Sydex CopyQM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class CopyQm + { + public ImageInfo Info => imageInfo; + public string Name => "Sydex CopyQM"; + public Guid Id => new Guid("147E927D-3A92-4E0C-82CD-142F5A4FA76D"); + public string Format => "Sydex CopyQM"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM/Read.cs b/DiscImageChef.DiscImages/CopyQM/Read.cs new file mode 100644 index 000000000..8accfb197 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM/Read.cs @@ -0,0 +1,216 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Sydex CopyQM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class CopyQm + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + byte[] hdr = new byte[133]; + + stream.Read(hdr, 0, 133); + header = new CopyQmHeader(); + IntPtr hdrPtr = Marshal.AllocHGlobal(133); + Marshal.Copy(hdr, 0, hdrPtr, 133); + header = (CopyQmHeader)Marshal.PtrToStructure(hdrPtr, typeof(CopyQmHeader)); + Marshal.FreeHGlobal(hdrPtr); + + DicConsole.DebugWriteLine("CopyQM plugin", "header.magic = 0x{0:X4}", header.magic); + DicConsole.DebugWriteLine("CopyQM plugin", "header.mark = 0x{0:X2}", header.mark); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorSize = {0}", header.sectorSize); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorPerCluster = {0}", header.sectorPerCluster); + DicConsole.DebugWriteLine("CopyQM plugin", "header.reservedSectors = {0}", header.reservedSectors); + DicConsole.DebugWriteLine("CopyQM plugin", "header.fatCopy = {0}", header.fatCopy); + DicConsole.DebugWriteLine("CopyQM plugin", "header.rootEntries = {0}", header.rootEntries); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectors = {0}", header.sectors); + DicConsole.DebugWriteLine("CopyQM plugin", "header.mediaType = 0x{0:X2}", header.mediaType); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerFat = {0}", header.sectorsPerFat); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerTrack = {0}", header.sectorsPerTrack); + DicConsole.DebugWriteLine("CopyQM plugin", "header.heads = {0}", header.heads); + DicConsole.DebugWriteLine("CopyQM plugin", "header.hidden = {0}", header.hidden); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsBig = {0}", header.sectorsBig); + DicConsole.DebugWriteLine("CopyQM plugin", "header.description = {0}", header.description); + DicConsole.DebugWriteLine("CopyQM plugin", "header.blind = {0}", header.blind); + DicConsole.DebugWriteLine("CopyQM plugin", "header.density = {0}", header.density); + DicConsole.DebugWriteLine("CopyQM plugin", "header.imageCylinders = {0}", header.imageCylinders); + DicConsole.DebugWriteLine("CopyQM plugin", "header.totalCylinders = {0}", header.totalCylinders); + DicConsole.DebugWriteLine("CopyQM plugin", "header.crc = 0x{0:X8}", header.crc); + DicConsole.DebugWriteLine("CopyQM plugin", "header.volumeLabel = {0}", header.volumeLabel); + DicConsole.DebugWriteLine("CopyQM plugin", "header.time = 0x{0:X4}", header.time); + DicConsole.DebugWriteLine("CopyQM plugin", "header.date = 0x{0:X4}", header.date); + DicConsole.DebugWriteLine("CopyQM plugin", "header.commentLength = {0}", header.commentLength); + DicConsole.DebugWriteLine("CopyQM plugin", "header.secbs = {0}", header.secbs); + DicConsole.DebugWriteLine("CopyQM plugin", "header.unknown = 0x{0:X4}", header.unknown); + DicConsole.DebugWriteLine("CopyQM plugin", "header.interleave = {0}", header.interleave); + DicConsole.DebugWriteLine("CopyQM plugin", "header.skew = {0}", header.skew); + DicConsole.DebugWriteLine("CopyQM plugin", "header.drive = {0}", header.drive); + + byte[] cmt = new byte[header.commentLength]; + stream.Read(cmt, 0, header.commentLength); + imageInfo.Comments = StringHandlers.CToString(cmt); + decodedImage = new MemoryStream(); + + calculatedDataCrc = 0; + + while(stream.Position + 2 < stream.Length) + { + byte[] runLengthBytes = new byte[2]; + if(stream.Read(runLengthBytes, 0, 2) != 2) break; + + short runLength = BitConverter.ToInt16(runLengthBytes, 0); + + if(runLength < 0) + { + byte repeatedByte = (byte)stream.ReadByte(); + byte[] repeatedArray = new byte[runLength * -1]; + ArrayHelpers.ArrayFill(repeatedArray, repeatedByte); + + for(int i = 0; i < runLength * -1; i++) + { + decodedImage.WriteByte(repeatedByte); + calculatedDataCrc = copyQmCrcTable[(repeatedByte ^ calculatedDataCrc) & 0x3F] ^ + (calculatedDataCrc >> 8); + } + } + else if(runLength > 0) + { + byte[] nonRepeated = new byte[runLength]; + stream.Read(nonRepeated, 0, runLength); + decodedImage.Write(nonRepeated, 0, runLength); + + foreach(byte c in nonRepeated) + calculatedDataCrc = copyQmCrcTable[(c ^ calculatedDataCrc) & 0x3F] ^ (calculatedDataCrc >> 8); + } + } + + // In case there is omitted data + long sectors = header.sectorsPerTrack * header.heads * header.totalCylinders; + + long fillingLen = sectors * header.sectorSize - decodedImage.Length; + + if(fillingLen > 0) + { + byte[] filling = new byte[fillingLen]; + ArrayHelpers.ArrayFill(filling, (byte)0xF6); + decodedImage.Write(filling, 0, filling.Length); + } + + int sum = 0; + for(int i = 0; i < hdr.Length - 1; i++) sum += hdr[i]; + + headerChecksumOk = ((-1 * sum) & 0xFF) == header.headerChecksum; + + DicConsole.DebugWriteLine("CopyQM plugin", "Calculated header checksum = 0x{0:X2}, {1}", (-1 * sum) & 0xFF, + headerChecksumOk); + DicConsole.DebugWriteLine("CopyQM plugin", "Calculated data CRC = 0x{0:X8}, {1}", calculatedDataCrc, + calculatedDataCrc == header.crc); + + imageInfo.Application = "CopyQM"; + imageInfo.CreationTime = DateHandlers.DosToDateTime(header.date, header.time); + imageInfo.LastModificationTime = imageInfo.CreationTime; + imageInfo.MediaTitle = header.volumeLabel; + imageInfo.ImageSize = (ulong)(stream.Length - 133 - header.commentLength); + imageInfo.Sectors = (ulong)sectors; + imageInfo.SectorSize = header.sectorSize; + + imageInfo.MediaType = Geometry.GetMediaType(((ushort)header.totalCylinders, (byte)header.heads, + header.sectorsPerTrack, (uint)header.sectorSize, + MediaEncoding.MFM, false)); + + switch(imageInfo.MediaType) + { + case MediaType.NEC_525_HD when header.drive == COPYQM_35_HD || header.drive == COPYQM_35_ED: + imageInfo.MediaType = MediaType.NEC_35_HD_8; + break; + case MediaType.DOS_525_HD when header.drive == COPYQM_35_HD || header.drive == COPYQM_35_ED: + imageInfo.MediaType = MediaType.NEC_35_HD_15; + break; + case MediaType.RX50 when header.drive == COPYQM_525_DD || header.drive == COPYQM_525_HD: + imageInfo.MediaType = MediaType.ATARI_35_SS_DD; + break; + } + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + decodedDisk = decodedImage.ToArray(); + + decodedImage.Close(); + + DicConsole.VerboseWriteLine("CopyQM image contains a disk of type {0}", imageInfo.MediaType); + if(!string.IsNullOrEmpty(imageInfo.Comments)) + DicConsole.VerboseWriteLine("CopyQM comments: {0}", imageInfo.Comments); + + imageInfo.Heads = header.heads; + imageInfo.Cylinders = header.totalCylinders; + imageInfo.SectorsPerTrack = header.sectorsPerTrack; + + return true; + } + + public bool? VerifyMediaImage() + { + return calculatedDataCrc == header.crc && headerChecksumOk; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Array.Copy(decodedDisk, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, + length * imageInfo.SectorSize); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM/Structs.cs b/DiscImageChef.DiscImages/CopyQM/Structs.cs new file mode 100644 index 000000000..5614160c0 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM/Structs.cs @@ -0,0 +1,109 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Sydex CopyQM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class CopyQm + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CopyQmHeader + { + /// <summary>0x00 magic, "CQ"</summary> + public ushort magic; + /// <summary>0x02 always 0x14</summary> + public byte mark; + /// <summary>0x03 Bytes per sector (part of FAT's BPB)</summary> + public ushort sectorSize; + /// <summary>0x05 Sectors per cluster (part of FAT's BPB)</summary> + public byte sectorPerCluster; + /// <summary>0x06 Reserved sectors (part of FAT's BPB)</summary> + public ushort reservedSectors; + /// <summary>0x08 Number of FAT copies (part of FAT's BPB)</summary> + public byte fatCopy; + /// <summary>0x09 Maximum number of entries in root directory (part of FAT's BPB)</summary> + public ushort rootEntries; + /// <summary>0x0B Sectors on disk (part of FAT's BPB)</summary> + public ushort sectors; + /// <summary>0x0D Media descriptor (part of FAT's BPB)</summary> + public byte mediaType; + /// <summary>0x0E Sectors per FAT (part of FAT's BPB)</summary> + public ushort sectorsPerFat; + /// <summary>0x10 Sectors per track (part of FAT's BPB)</summary> + public ushort sectorsPerTrack; + /// <summary>0x12 Heads (part of FAT's BPB)</summary> + public ushort heads; + /// <summary>0x14 Hidden sectors (part of FAT's BPB)</summary> + public uint hidden; + /// <summary>0x18 Sectors on disk (part of FAT's BPB)</summary> + public uint sectorsBig; + /// <summary>0x1C Description</summary> + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)] + public string description; + /// <summary>0x58 Blind mode. 0 = DOS, 1 = blind, 2 = HFS</summary> + public byte blind; + /// <summary>0x59 Density. 0 = Double, 1 = High, 2 = Quad/Extra</summary> + public byte density; + /// <summary>0x5A Cylinders in image</summary> + public byte imageCylinders; + /// <summary>0x5B Cylinders on disk</summary> + public byte totalCylinders; + /// <summary>0x5C CRC32 of data</summary> + public uint crc; + /// <summary>0x60 DOS volume label</summary> + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] + public string volumeLabel; + /// <summary>0x6B Modification time</summary> + public ushort time; + /// <summary>0x6D Modification date</summary> + public ushort date; + /// <summary>0x6F Comment length</summary> + public ushort commentLength; + /// <summary>0x71 Sector base (first sector - 1)</summary> + public byte secbs; + /// <summary>0x72 Unknown</summary> + public ushort unknown; + /// <summary>0x74 Interleave</summary> + public byte interleave; + /// <summary>0x75 Skew</summary> + public byte skew; + /// <summary>0x76 Source drive type. 1 = 5.25" DD, 2 = 5.25" HD, 3 = 3.5" DD, 4 = 3.5" HD, 6 = 3.5" ED</summary> + public byte drive; + /// <summary>0x77 Filling bytes</summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public byte[] fill; + /// <summary>0x84 Header checksum</summary> + public byte headerChecksum; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM/Unsupported.cs b/DiscImageChef.DiscImages/CopyQM/Unsupported.cs new file mode 100644 index 000000000..9ab52b415 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM/Unsupported.cs @@ -0,0 +1,139 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Sydex CopyQM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class CopyQm + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88/Constants.cs b/DiscImageChef.DiscImages/D88/Constants.cs new file mode 100644 index 000000000..ec304441b --- /dev/null +++ b/DiscImageChef.DiscImages/D88/Constants.cs @@ -0,0 +1,40 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Quasi88 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class D88 + { + const byte READ_ONLY = 0x10; + readonly byte[] reservedEmpty = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88/D88.cs b/DiscImageChef.DiscImages/D88/D88.cs new file mode 100644 index 000000000..250fcd12e --- /dev/null +++ b/DiscImageChef.DiscImages/D88/D88.cs @@ -0,0 +1,75 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : D88.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Quasi88 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // Information from Quasi88's FORMAT.TXT file + // Japanese comments copied from there + // TODO: Solve media types + public partial class D88 : IMediaImage + { + ImageInfo imageInfo; + List<byte[]> sectorsData; + + public D88() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88/Enums.cs b/DiscImageChef.DiscImages/D88/Enums.cs new file mode 100644 index 000000000..a5ab2ad8e --- /dev/null +++ b/DiscImageChef.DiscImages/D88/Enums.cs @@ -0,0 +1,88 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Quasi88 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class D88 + { + enum DiskType : byte + { + D2 = 0x00, + Dd2 = 0x10, + Hd2 = 0x20 + } + + enum DensityType : byte + { + Mfm = 0x00, + Fm = 0x40 + } + + /// <summary> + /// Status as returned by PC-98 BIOS + /// ステータスは、PC-98x1 のBIOS が返してくるステータスで、 + /// </summary> + enum StatusType : byte + { + /// <summary> + /// Normal + /// 正常 + /// </summary> + Normal = 0x00, + /// <summary> + /// Deleted + /// 正常(DELETED DATA) + /// </summary> + Deleted = 0x10, + /// <summary> + /// CRC error in address fields + /// ID CRC エラー + /// </summary> + IdError = 0xA0, + /// <summary> + /// CRC error in data block + /// データ CRC エラー + /// </summary> + DataError = 0xB0, + /// <summary> + /// Address mark not found + /// アドレスマークなし + /// </summary> + AddressMarkNotFound = 0xE0, + /// <summary> + /// Data mark not found + /// データマークなし + /// </summary> + DataMarkNotFound = 0xF0 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88/Identify.cs b/DiscImageChef.DiscImages/D88/Identify.cs new file mode 100644 index 000000000..bdc10514a --- /dev/null +++ b/DiscImageChef.DiscImages/D88/Identify.cs @@ -0,0 +1,91 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Quasi88 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class D88 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + // Even if disk name is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p + Encoding shiftjis = Encoding.GetEncoding("shift_jis"); + + D88Header d88Hdr = new D88Header(); + + if(stream.Length < Marshal.SizeOf(d88Hdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(d88Hdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + d88Hdr = (D88Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(D88Header)); + handle.Free(); + + DicConsole.DebugWriteLine("D88 plugin", "d88hdr.name = \"{0}\"", + StringHandlers.CToString(d88Hdr.name, shiftjis)); + DicConsole.DebugWriteLine("D88 plugin", "d88hdr.reserved is empty? = {0}", + d88Hdr.reserved.SequenceEqual(reservedEmpty)); + DicConsole.DebugWriteLine("D88 plugin", "d88hdr.write_protect = 0x{0:X2}", d88Hdr.write_protect); + DicConsole.DebugWriteLine("D88 plugin", "d88hdr.disk_type = {0} ({1})", d88Hdr.disk_type, + (byte)d88Hdr.disk_type); + DicConsole.DebugWriteLine("D88 plugin", "d88hdr.disk_size = {0}", d88Hdr.disk_size); + + if(d88Hdr.disk_size != stream.Length) return false; + + if(d88Hdr.disk_type != DiskType.D2 && d88Hdr.disk_type != DiskType.Dd2 && + d88Hdr.disk_type != DiskType.Hd2) return false; + + if(!d88Hdr.reserved.SequenceEqual(reservedEmpty)) return false; + + int counter = 0; + foreach(int t in d88Hdr.track_table) + { + if(t > 0) counter++; + + if(t < 0 || t > stream.Length) return false; + } + + DicConsole.DebugWriteLine("D88 plugin", "{0} tracks", counter); + + return counter > 0; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88/Properties.cs b/DiscImageChef.DiscImages/D88/Properties.cs new file mode 100644 index 000000000..355fbc223 --- /dev/null +++ b/DiscImageChef.DiscImages/D88/Properties.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Quasi88 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class D88 + { + public string Name => "D88 Disk Image"; + public Guid Id => new Guid("669EDC77-EC41-4720-A88C-49C38CFFBAA0"); + public ImageInfo Info => imageInfo; + public string Format => "D88 disk image"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88.cs b/DiscImageChef.DiscImages/D88/Read.cs similarity index 54% rename from DiscImageChef.DiscImages/D88.cs rename to DiscImageChef.DiscImages/D88/Read.cs index 90d352b7f..926dcc5c6 100644 --- a/DiscImageChef.DiscImages/D88.cs +++ b/DiscImageChef.DiscImages/D88/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : D88.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Quasi88 disk images. +// Reads Quasi88 disk images. // // --[ License ] -------------------------------------------------------------- // @@ -38,116 +38,14 @@ using System.Runtime.InteropServices; using System.Text; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Decoders.Floppy; -using Schemas; namespace DiscImageChef.DiscImages { - // Information from Quasi88's FORMAT.TXT file - // Japanese comments copied from there - // TODO: Solve media types - public class D88 : IMediaImage + public partial class D88 { - const byte READ_ONLY = 0x10; - - readonly byte[] reservedEmpty = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - ImageInfo imageInfo; - - List<byte[]> sectorsData; - - public D88() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "D88 Disk Image"; - public Guid Id => new Guid("669EDC77-EC41-4720-A88C-49C38CFFBAA0"); - public ImageInfo Info => imageInfo; - - public string Format => "D88 disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - // Even if disk name is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p - Encoding shiftjis = Encoding.GetEncoding("shift_jis"); - - D88Header d88Hdr = new D88Header(); - - if(stream.Length < Marshal.SizeOf(d88Hdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(d88Hdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - d88Hdr = (D88Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(D88Header)); - handle.Free(); - - DicConsole.DebugWriteLine("D88 plugin", "d88hdr.name = \"{0}\"", - StringHandlers.CToString(d88Hdr.name, shiftjis)); - DicConsole.DebugWriteLine("D88 plugin", "d88hdr.reserved is empty? = {0}", - d88Hdr.reserved.SequenceEqual(reservedEmpty)); - DicConsole.DebugWriteLine("D88 plugin", "d88hdr.write_protect = 0x{0:X2}", d88Hdr.write_protect); - DicConsole.DebugWriteLine("D88 plugin", "d88hdr.disk_type = {0} ({1})", d88Hdr.disk_type, - (byte)d88Hdr.disk_type); - DicConsole.DebugWriteLine("D88 plugin", "d88hdr.disk_size = {0}", d88Hdr.disk_size); - - if(d88Hdr.disk_size != stream.Length) return false; - - if(d88Hdr.disk_type != DiskType.D2 && d88Hdr.disk_type != DiskType.Dd2 && - d88Hdr.disk_type != DiskType.Hd2) return false; - - if(!d88Hdr.reserved.SequenceEqual(reservedEmpty)) return false; - - int counter = 0; - foreach(int t in d88Hdr.track_table) - { - if(t > 0) counter++; - - if(t < 0 || t > stream.Length) return false; - } - - DicConsole.DebugWriteLine("D88 plugin", "{0} tracks", counter); - - return counter > 0; - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -444,249 +342,5 @@ namespace DiscImageChef.DiscImages return buffer.ToArray(); } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - enum DiskType : byte - { - D2 = 0x00, - Dd2 = 0x10, - Hd2 = 0x20 - } - - enum DensityType : byte - { - Mfm = 0x00, - Fm = 0x40 - } - - /// <summary> - /// Status as returned by PC-98 BIOS - /// ステータスは、PC-98x1 のBIOS が返してくるステータスで、 - /// </summary> - enum StatusType : byte - { - /// <summary> - /// Normal - /// 正常 - /// </summary> - Normal = 0x00, - /// <summary> - /// Deleted - /// 正常(DELETED DATA) - /// </summary> - Deleted = 0x10, - /// <summary> - /// CRC error in address fields - /// ID CRC エラー - /// </summary> - IdError = 0xA0, - /// <summary> - /// CRC error in data block - /// データ CRC エラー - /// </summary> - DataError = 0xB0, - /// <summary> - /// Address mark not found - /// アドレスマークなし - /// </summary> - AddressMarkNotFound = 0xE0, - /// <summary> - /// Data mark not found - /// データマークなし - /// </summary> - DataMarkNotFound = 0xF0 - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct D88Header - { - /// <summary> - /// Disk name, nul-terminated ASCII - /// ディスクの名前(ASCII + '\0') - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] - public byte[] name; - /// <summary> - /// Reserved - /// ディスクの名前(ASCII + '\0') - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] - public byte[] reserved; - /// <summary> - /// Write protect status - /// ライトプロテクト: 0x00 なし、0x10 あり - /// </summary> - public byte write_protect; - /// <summary> - /// Disk type - /// ディスクの種類: 0x00 2D、 0x10 2DD、 0x20 2HD - /// </summary> - public DiskType disk_type; - /// <summary> - /// Disk image size - /// ディスクのサイズ - /// </summary> - public int disk_size; - /// <summary> - /// Track pointers - /// トラック部のオフセットテーブル 0 Track ~ 163 Track - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 164)] - public int[] track_table; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct SectorHeader - { - /// <summary> - /// Cylinder - /// ID の C - /// </summary> - public byte c; - /// <summary> - /// Head - /// ID の H - /// </summary> - public byte h; - /// <summary> - /// Sector number - /// ID の R - /// </summary> - public byte r; - /// <summary> - /// Sector size - /// ID の N - /// </summary> - public IBMSectorSizeCode n; - /// <summary> - /// Number of sectors in this track - /// このトラック内に存在するセクタの数 - /// </summary> - public short spt; - /// <summary> - /// Density: 0x00 MFM, 0x40 FM - /// 記録密度: 0x00 倍密度、0x40 単密度 - /// </summary> - public DensityType density; - /// <summary> - /// Deleted sector, 0x00 not deleted, 0x10 deleted - /// DELETED MARK: 0x00 ノーマル、 0x10 DELETED - /// </summary> - public byte deleted_mark; - /// <summary> - /// Sector status - /// ステータス - /// </summary> - public byte status; - /// <summary> - /// Reserved - /// リザーブ - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] - public byte[] reserved; - /// <summary> - /// Size of data following this field - /// このセクタ部のデータサイズ - /// </summary> - public short size_of_data; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88/Structs.cs b/DiscImageChef.DiscImages/D88/Structs.cs new file mode 100644 index 000000000..80ceb6cc9 --- /dev/null +++ b/DiscImageChef.DiscImages/D88/Structs.cs @@ -0,0 +1,134 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Quasi88 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; +using DiscImageChef.Decoders.Floppy; + +namespace DiscImageChef.DiscImages +{ + public partial class D88 + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct D88Header + { + /// <summary> + /// Disk name, nul-terminated ASCII + /// ディスクの名前(ASCII + '\0') + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] + public byte[] name; + /// <summary> + /// Reserved + /// ディスクの名前(ASCII + '\0') + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] + public byte[] reserved; + /// <summary> + /// Write protect status + /// ライトプロテクト: 0x00 なし、0x10 あり + /// </summary> + public byte write_protect; + /// <summary> + /// Disk type + /// ディスクの種類: 0x00 2D、 0x10 2DD、 0x20 2HD + /// </summary> + public DiskType disk_type; + /// <summary> + /// Disk image size + /// ディスクのサイズ + /// </summary> + public int disk_size; + /// <summary> + /// Track pointers + /// トラック部のオフセットテーブル 0 Track ~ 163 Track + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 164)] + public int[] track_table; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SectorHeader + { + /// <summary> + /// Cylinder + /// ID の C + /// </summary> + public byte c; + /// <summary> + /// Head + /// ID の H + /// </summary> + public byte h; + /// <summary> + /// Sector number + /// ID の R + /// </summary> + public byte r; + /// <summary> + /// Sector size + /// ID の N + /// </summary> + public IBMSectorSizeCode n; + /// <summary> + /// Number of sectors in this track + /// このトラック内に存在するセクタの数 + /// </summary> + public short spt; + /// <summary> + /// Density: 0x00 MFM, 0x40 FM + /// 記録密度: 0x00 倍密度、0x40 単密度 + /// </summary> + public DensityType density; + /// <summary> + /// Deleted sector, 0x00 not deleted, 0x10 deleted + /// DELETED MARK: 0x00 ノーマル、 0x10 DELETED + /// </summary> + public byte deleted_mark; + /// <summary> + /// Sector status + /// ステータス + /// </summary> + public byte status; + /// <summary> + /// Reserved + /// リザーブ + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public byte[] reserved; + /// <summary> + /// Size of data following this field + /// このセクタ部のデータサイズ + /// </summary> + public short size_of_data; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/D88/Unsupported.cs b/DiscImageChef.DiscImages/D88/Unsupported.cs new file mode 100644 index 000000000..0009e6b30 --- /dev/null +++ b/DiscImageChef.DiscImages/D88/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Quasi88 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class D88 + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DART/Constants.cs b/DiscImageChef.DiscImages/DART/Constants.cs new file mode 100644 index 000000000..fc3770919 --- /dev/null +++ b/DiscImageChef.DiscImages/DART/Constants.cs @@ -0,0 +1,73 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Apple Disk Archival/Retrieval Tool format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Dart + { + // Disk types + const byte DISK_MAC = 1; + const byte DISK_LISA = 2; + const byte DISK_APPLE2 = 3; + const byte DISK_MAC_HD = 16; + const byte DISK_DOS = 17; + const byte DISK_DOS_HD = 18; + + // Compression types + // "fast" + const byte COMPRESS_RLE = 0; + // "best" + const byte COMPRESS_LZH = 1; + // DART <= 1.4 + const byte COMPRESS_NONE = 2; + + // Valid sizes + const short SIZE_LISA = 400; + const short SIZE_MAC_SS = 400; + const short SIZE_MAC = 800; + const short SIZE_MAC_HD = 1440; + const short SIZE_APPLE2 = 800; + const short SIZE_DOS = 720; + const short SIZE_DOS_HD = 1440; + + // bLength array sizes + const int BLOCK_ARRAY_LEN_LOW = 40; + const int BLOCK_ARRAY_LEN_HIGH = 72; + + const int SECTORS_PER_BLOCK = 40; + const int SECTOR_SIZE = 512; + const int TAG_SECTOR_SIZE = 12; + const int DATA_SIZE = SECTORS_PER_BLOCK * SECTOR_SIZE; + const int TAG_SIZE = SECTORS_PER_BLOCK * TAG_SECTOR_SIZE; + const int BUFFER_SIZE = SECTORS_PER_BLOCK * SECTOR_SIZE + SECTORS_PER_BLOCK * TAG_SECTOR_SIZE; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DART/DART.cs b/DiscImageChef.DiscImages/DART/DART.cs new file mode 100644 index 000000000..b1296c0e9 --- /dev/null +++ b/DiscImageChef.DiscImages/DART/DART.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : DART.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Apple Disk Archival/Retrieval Tool format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Dart : IMediaImage + { + // DART images are at most 1474560 bytes, so let's cache the whole + byte[] dataCache; + uint dataChecksum; + ImageInfo imageInfo; + byte[] tagCache; + uint tagChecksum; + + public Dart() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DART/Identify.cs b/DiscImageChef.DiscImages/DART/Identify.cs new file mode 100644 index 000000000..676f7f6e8 --- /dev/null +++ b/DiscImageChef.DiscImages/DART/Identify.cs @@ -0,0 +1,92 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Apple Disk Archival/Retrieval Tool format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Dart + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if(stream.Length < 84) return false; + + DartHeader header = new DartHeader(); + stream.Seek(0, SeekOrigin.Begin); + byte[] headerB = new byte[Marshal.SizeOf(header)]; + + stream.Read(headerB, 0, Marshal.SizeOf(header)); + header = BigEndianMarshal.ByteArrayToStructureBigEndian<DartHeader>(headerB); + + if(header.srcCmp > COMPRESS_NONE) return false; + + int expectedMaxSize = 84 + header.srcSize * 2 * 524; + + switch(header.srcType) + { + case DISK_MAC: + if(header.srcSize != SIZE_MAC_SS && header.srcSize != SIZE_MAC) return false; + + break; + case DISK_LISA: + if(header.srcSize != SIZE_LISA) return false; + + break; + case DISK_APPLE2: + if(header.srcSize != SIZE_APPLE2) return false; + + break; + case DISK_MAC_HD: + if(header.srcSize != SIZE_MAC_HD) return false; + + expectedMaxSize += 64; + break; + case DISK_DOS: + if(header.srcSize != SIZE_DOS) return false; + + break; + case DISK_DOS_HD: + if(header.srcSize != SIZE_DOS_HD) return false; + + expectedMaxSize += 64; + break; + default: return false; + } + + return stream.Length <= expectedMaxSize; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DART/Properties.cs b/DiscImageChef.DiscImages/DART/Properties.cs new file mode 100644 index 000000000..c74143574 --- /dev/null +++ b/DiscImageChef.DiscImages/DART/Properties.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Apple Disk Archival/Retrieval Tool format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Dart + { + public string Name => "Apple Disk Archival/Retrieval Tool"; + public Guid Id => new Guid("B3E06BF8-F98D-4F9B-BBE2-342C373BAF3E"); + public ImageInfo Info => imageInfo; + public string Format => "Apple Disk Archival/Retrieval Tool"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DART.cs b/DiscImageChef.DiscImages/DART/Read.cs similarity index 64% rename from DiscImageChef.DiscImages/DART.cs rename to DiscImageChef.DiscImages/DART/Read.cs index acba2c5be..eb9626a95 100644 --- a/DiscImageChef.DiscImages/DART.cs +++ b/DiscImageChef.DiscImages/DART/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : DART.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Apple Disk Archival/Retrieval Tool format. +// Reads Apple Disk Archival/Retrieval Tool format. // // --[ License ] -------------------------------------------------------------- // @@ -31,162 +31,23 @@ // ****************************************************************************/ using System; -using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; -using System.Text; using System.Text.RegularExpressions; +using Claunia.Encoding; using Claunia.RsrcFork; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Compression; using DiscImageChef.Console; -using Schemas; using Version = Resources.Version; namespace DiscImageChef.DiscImages { - public class Dart : IMediaImage + public partial class Dart { - // Disk types - const byte DISK_MAC = 1; - const byte DISK_LISA = 2; - const byte DISK_APPLE2 = 3; - const byte DISK_MAC_HD = 16; - const byte DISK_DOS = 17; - const byte DISK_DOS_HD = 18; - - // Compression types - // "fast" - const byte COMPRESS_RLE = 0; - // "best" - const byte COMPRESS_LZH = 1; - // DART <= 1.4 - const byte COMPRESS_NONE = 2; - - // Valid sizes - const short SIZE_LISA = 400; - const short SIZE_MAC_SS = 400; - const short SIZE_MAC = 800; - const short SIZE_MAC_HD = 1440; - const short SIZE_APPLE2 = 800; - const short SIZE_DOS = 720; - const short SIZE_DOS_HD = 1440; - - // bLength array sizes - const int BLOCK_ARRAY_LEN_LOW = 40; - const int BLOCK_ARRAY_LEN_HIGH = 72; - - const int SECTORS_PER_BLOCK = 40; - const int SECTOR_SIZE = 512; - const int TAG_SECTOR_SIZE = 12; - const int DATA_SIZE = SECTORS_PER_BLOCK * SECTOR_SIZE; - const int TAG_SIZE = SECTORS_PER_BLOCK * TAG_SECTOR_SIZE; - const int BUFFER_SIZE = SECTORS_PER_BLOCK * SECTOR_SIZE + SECTORS_PER_BLOCK * TAG_SECTOR_SIZE; - - // DART images are at most 1474560 bytes, so let's cache the whole - byte[] dataCache; - uint dataChecksum; - ImageInfo imageInfo; - byte[] tagCache; - uint tagChecksum; - - public Dart() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "Apple Disk Archival/Retrieval Tool"; - public Guid Id => new Guid("B3E06BF8-F98D-4F9B-BBE2-342C373BAF3E"); - public ImageInfo Info => imageInfo; - - public string Format => "Apple Disk Archival/Retrieval Tool"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if(stream.Length < 84) return false; - - DartHeader header = new DartHeader(); - stream.Seek(0, SeekOrigin.Begin); - byte[] headerB = new byte[Marshal.SizeOf(header)]; - - stream.Read(headerB, 0, Marshal.SizeOf(header)); - header = BigEndianMarshal.ByteArrayToStructureBigEndian<DartHeader>(headerB); - - if(header.srcCmp > COMPRESS_NONE) return false; - - int expectedMaxSize = 84 + header.srcSize * 2 * 524; - - switch(header.srcType) - { - case DISK_MAC: - if(header.srcSize != SIZE_MAC_SS && header.srcSize != SIZE_MAC) return false; - - break; - case DISK_LISA: - if(header.srcSize != SIZE_LISA) return false; - - break; - case DISK_APPLE2: - if(header.srcSize != SIZE_APPLE2) return false; - - break; - case DISK_MAC_HD: - if(header.srcSize != SIZE_MAC_HD) return false; - - expectedMaxSize += 64; - break; - case DISK_DOS: - if(header.srcSize != SIZE_DOS) return false; - - break; - case DISK_DOS_HD: - if(header.srcSize != SIZE_DOS_HD) return false; - - expectedMaxSize += 64; - break; - default: return false; - } - - return stream.Length <= expectedMaxSize; - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -499,92 +360,5 @@ namespace DiscImageChef.DiscImages return buffer; } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DartHeader - { - public byte srcCmp; - public byte srcType; - public short srcSize; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DART/Structs.cs b/DiscImageChef.DiscImages/DART/Structs.cs new file mode 100644 index 000000000..49a62771d --- /dev/null +++ b/DiscImageChef.DiscImages/DART/Structs.cs @@ -0,0 +1,47 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Apple Disk Archival/Retrieval Tool format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Dart + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DartHeader + { + public byte srcCmp; + public byte srcType; + public short srcSize; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DART/Unsupported.cs b/DiscImageChef.DiscImages/DART/Unsupported.cs new file mode 100644 index 000000000..d50c16cda --- /dev/null +++ b/DiscImageChef.DiscImages/DART/Unsupported.cs @@ -0,0 +1,118 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Apple Disk Archival/Retrieval Tool format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Dart + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DIM/Constants.cs b/DiscImageChef.DiscImages/DIM/Constants.cs new file mode 100644 index 000000000..9f10de373 --- /dev/null +++ b/DiscImageChef.DiscImages/DIM/Constants.cs @@ -0,0 +1,41 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for DIM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Dim + { + /// <summary>Start of data sectors in disk image, should be 0x100</summary> + const uint DATA_OFFSET = 0x100; + readonly byte[] headerId = {0x44, 0x49, 0x46, 0x43, 0x20, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x20}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DIM/DIM.cs b/DiscImageChef.DiscImages/DIM/DIM.cs new file mode 100644 index 000000000..f3b79f607 --- /dev/null +++ b/DiscImageChef.DiscImages/DIM/DIM.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : DIM.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages DIM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: What are the real supported floppies for this image format? + public partial class Dim : IMediaImage + { + byte[] comment; + IFilter dimImageFilter; + DiskType dskType; + byte[] hdrId; + ImageInfo imageInfo; + + public Dim() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DIM/Enums.cs b/DiscImageChef.DiscImages/DIM/Enums.cs new file mode 100644 index 000000000..6fb415fcc --- /dev/null +++ b/DiscImageChef.DiscImages/DIM/Enums.cs @@ -0,0 +1,47 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for DIM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Dim + { + enum DiskType : byte + { + Hd2 = 0, + Hs2 = 1, + Hc2 = 2, + Hde2 = 3, + Hq2 = 9, + N88 = 17 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DIM/Identify.cs b/DiscImageChef.DiscImages/DIM/Identify.cs new file mode 100644 index 000000000..dbec32a86 --- /dev/null +++ b/DiscImageChef.DiscImages/DIM/Identify.cs @@ -0,0 +1,60 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies DIM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Dim + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < DATA_OFFSET) return false; + + comment = new byte[60]; + hdrId = new byte[13]; + stream.Seek(0, SeekOrigin.Begin); + dskType = (DiskType)stream.ReadByte(); + stream.Seek(0xAB, SeekOrigin.Begin); + stream.Read(hdrId, 0, 13); + stream.Seek(0xC2, SeekOrigin.Begin); + stream.Read(comment, 0, 60); + + return headerId.SequenceEqual(hdrId); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DIM/Properties.cs b/DiscImageChef.DiscImages/DIM/Properties.cs new file mode 100644 index 000000000..7a3bab9c5 --- /dev/null +++ b/DiscImageChef.DiscImages/DIM/Properties.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for DIM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Dim + { + public string Name => "DIM Disk Image"; + public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88"); + public ImageInfo Info => imageInfo; + public string Format => "DIM disk image"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DIM.cs b/DiscImageChef.DiscImages/DIM/Read.cs similarity index 56% rename from DiscImageChef.DiscImages/DIM.cs rename to DiscImageChef.DiscImages/DIM/Read.cs index ae8fe3248..33459064e 100644 --- a/DiscImageChef.DiscImages/DIM.cs +++ b/DiscImageChef.DiscImages/DIM/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : DIM.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages DIM disk images. +// Reads DIM disk images. // // --[ License ] -------------------------------------------------------------- // @@ -31,95 +31,18 @@ // ****************************************************************************/ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; namespace DiscImageChef.DiscImages { - // TODO: What are the real supported floppies for this image format? - public class Dim : IMediaImage + public partial class Dim { - /// <summary>Start of data sectors in disk image, should be 0x100</summary> - const uint DATA_OFFSET = 0x100; - - readonly byte[] headerId = {0x44, 0x49, 0x46, 0x43, 0x20, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x20}; - byte[] comment; - /// <summary>Disk image file</summary> - IFilter dimImageFilter; - DiskType dskType; - byte[] hdrId; - ImageInfo imageInfo; - - public Dim() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "DIM Disk Image"; - public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88"); - public ImageInfo Info => imageInfo; - - public string Format => "DIM disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < DATA_OFFSET) return false; - - comment = new byte[60]; - hdrId = new byte[13]; - stream.Seek(0, SeekOrigin.Begin); - dskType = (DiskType)stream.ReadByte(); - stream.Seek(0xAB, SeekOrigin.Begin); - stream.Read(hdrId, 0, 13); - stream.Seek(0xC2, SeekOrigin.Begin); - stream.Read(comment, 0, 60); - - return headerId.SequenceEqual(hdrId); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -296,114 +219,5 @@ namespace DiscImageChef.DiscImages return buffer; } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - enum DiskType : byte - { - Hd2 = 0, - Hs2 = 1, - Hc2 = 2, - Hde2 = 3, - Hq2 = 9, - N88 = 17 - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DIM/Unsupported.cs b/DiscImageChef.DiscImages/DIM/Unsupported.cs new file mode 100644 index 000000000..f5749ed6a --- /dev/null +++ b/DiscImageChef.DiscImages/DIM/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by DIM disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Dim + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscFerret/Constants.cs b/DiscImageChef.DiscImages/DiscFerret/Constants.cs new file mode 100644 index 000000000..c06e35b02 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscFerret/Constants.cs @@ -0,0 +1,46 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for DiscFerret flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class DiscFerret + { + /// <summary> + /// "DFER" + /// </summary> + const uint DFI_MAGIC = 0x52454644; + /// <summary> + /// "DFE2" + /// </summary> + const uint DFI_MAGIC2 = 0x32454644; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscFerret/DiscFerret.cs b/DiscImageChef.DiscImages/DiscFerret/DiscFerret.cs new file mode 100644 index 000000000..305b172ed --- /dev/null +++ b/DiscImageChef.DiscImages/DiscFerret/DiscFerret.cs @@ -0,0 +1,74 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : DiscFerret.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages DiscFerret flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscFerret : IMediaImage + { + ImageInfo imageInfo; + // TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0 + public SortedDictionary<int, long> TrackLengths; + public SortedDictionary<int, long> TrackOffsets; + + public DiscFerret() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscFerret/Identify.cs b/DiscImageChef.DiscImages/DiscFerret/Identify.cs new file mode 100644 index 000000000..832727f5c --- /dev/null +++ b/DiscImageChef.DiscImages/DiscFerret/Identify.cs @@ -0,0 +1,51 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies DiscFerret flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscFerret + { + public bool Identify(IFilter imageFilter) + { + byte[] magicB = new byte[4]; + Stream stream = imageFilter.GetDataForkStream(); + stream.Read(magicB, 0, 4); + uint magic = BitConverter.ToUInt32(magicB, 0); + + return magic == DFI_MAGIC || magic == DFI_MAGIC2; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscFerret/Properties.cs b/DiscImageChef.DiscImages/DiscFerret/Properties.cs new file mode 100644 index 000000000..036dfe641 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscFerret/Properties.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for DiscFerret flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscFerret + { + public string Name => "DiscFerret"; + public Guid Id => new Guid("70EA7B9B-5323-42EB-9B40-8DDA37C5EB4D"); + public ImageInfo Info => imageInfo; + public string Format => "DiscFerret"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscFerret.cs b/DiscImageChef.DiscImages/DiscFerret/Read.cs similarity index 54% rename from DiscImageChef.DiscImages/DiscFerret.cs rename to DiscImageChef.DiscImages/DiscFerret/Read.cs index 67faefc24..29ba8b963 100644 --- a/DiscImageChef.DiscImages/DiscFerret.cs +++ b/DiscImageChef.DiscImages/DiscFerret/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : DiscFerret.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages DiscFerret flux images. +// Reads DiscFerret flux images. // // --[ License ] -------------------------------------------------------------- // @@ -34,83 +34,14 @@ using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; namespace DiscImageChef.DiscImages { - public class DiscFerret : IMediaImage + public partial class DiscFerret { - /// <summary> - /// "DFER" - /// </summary> - const uint DFI_MAGIC = 0x52454644; - /// <summary> - /// "DFE2" - /// </summary> - const uint DFI_MAGIC2 = 0x32454644; - ImageInfo imageInfo; - // TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0 - public SortedDictionary<int, long> TrackLengths; - public SortedDictionary<int, long> TrackOffsets; - - public DiscFerret() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "DiscFerret"; - public Guid Id => new Guid("70EA7B9B-5323-42EB-9B40-8DDA37C5EB4D"); - public ImageInfo Info => imageInfo; - - public string Format => "DiscFerret"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - byte[] magicB = new byte[4]; - Stream stream = imageFilter.GetDataForkStream(); - stream.Read(magicB, 0, 4); - uint magic = BitConverter.ToUInt32(magicB, 0); - - return magic == DFI_MAGIC || magic == DFI_MAGIC2; - } - public bool Open(IFilter imageFilter) { byte[] magicB = new byte[4]; @@ -212,11 +143,6 @@ namespace DiscImageChef.DiscImages throw new NotImplementedException("Flux decoding is not yet implemented."); } - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) { throw new NotImplementedException("Flux decoding is not yet implemented."); @@ -232,68 +158,5 @@ namespace DiscImageChef.DiscImages { throw new NotImplementedException("Flux decoding is not yet implemented."); } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DfiBlockHeader - { - public ushort cylinder; - public ushort head; - public ushort sector; - public uint length; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscFerret/Structs.cs b/DiscImageChef.DiscImages/DiscFerret/Structs.cs new file mode 100644 index 000000000..d135111a4 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscFerret/Structs.cs @@ -0,0 +1,48 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for DiscFerret flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscFerret + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DfiBlockHeader + { + public ushort cylinder; + public ushort head; + public ushort sector; + public uint length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscFerret/Unsupported.cs b/DiscImageChef.DiscImages/DiscFerret/Unsupported.cs new file mode 100644 index 000000000..40dab6158 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscFerret/Unsupported.cs @@ -0,0 +1,98 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by DiscFerret flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscFerret + { + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index e58e84de4..938775e92 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -57,59 +57,426 @@ <PackageReference Include="Unclassified.NetRevisionTask" Version="0.2.2-beta" /> </ItemGroup> <ItemGroup> - <Compile Include="DiscFerret.cs" /> - <Compile Include="DiscImageChef.cs" /> - <Compile Include="HDCopy.cs" /> - <Compile Include="KryoFlux.cs" /> - <Compile Include="Apple2MG.cs" /> - <Compile Include="CDRWin.cs" /> - <Compile Include="DiskCopy42.cs" /> - <Compile Include="Nero.cs" /> + <Compile Include="Alcohol120\Alcohol120.cs" /> + <Compile Include="Alcohol120\Constants.cs" /> + <Compile Include="Alcohol120\Enums.cs" /> + <Compile Include="Alcohol120\Helpers.cs" /> + <Compile Include="Alcohol120\Identify.cs" /> + <Compile Include="Alcohol120\Properties.cs" /> + <Compile Include="Alcohol120\Read.cs" /> + <Compile Include="Alcohol120\Structs.cs" /> + <Compile Include="Alcohol120\Write.cs" /> + <Compile Include="Anex86\Anex86.cs" /> + <Compile Include="Anex86\Identify.cs" /> + <Compile Include="Anex86\Properties.cs" /> + <Compile Include="Anex86\Read.cs" /> + <Compile Include="Anex86\Structs.cs" /> + <Compile Include="Anex86\Unsupported.cs" /> + <Compile Include="Anex86\Write.cs" /> + <Compile Include="Apple2MG\Constants.cs" /> + <Compile Include="Apple2MG\Enums.cs" /> + <Compile Include="Apple2MG\Helpers.cs" /> + <Compile Include="Apple2MG\Identify.cs" /> + <Compile Include="Apple2MG\Properties.cs" /> + <Compile Include="Apple2MG\Read.cs" /> + <Compile Include="Apple2MG\Structs.cs" /> + <Compile Include="Apple2MG\Unsupported.cs" /> + <Compile Include="Apple2MG\Write.cs" /> + <Compile Include="AppleDOS\Constants.cs" /> + <Compile Include="AppleDOS\Identify.cs" /> + <Compile Include="AppleDOS\Properties.cs" /> + <Compile Include="AppleDOS\Read.cs" /> + <Compile Include="AppleDOS\Unsupported.cs" /> + <Compile Include="AppleDOS\Write.cs" /> + <Compile Include="AppleNIB\Constants.cs" /> + <Compile Include="AppleNIB\Helpers.cs" /> + <Compile Include="AppleNIB\Identify.cs" /> + <Compile Include="AppleNIB\Properties.cs" /> + <Compile Include="AppleNIB\Read.cs" /> + <Compile Include="AppleNIB\Unsupported.cs" /> + <Compile Include="Apridisk\Compression.cs" /> + <Compile Include="Apridisk\Constants.cs" /> + <Compile Include="Apridisk\Enums.cs" /> + <Compile Include="Apridisk\Helpers.cs" /> + <Compile Include="Apridisk\Identify.cs" /> + <Compile Include="Apridisk\Properties.cs" /> + <Compile Include="Apridisk\Read.cs" /> + <Compile Include="Apridisk\Structs.cs" /> + <Compile Include="Apridisk\Unsupported.cs" /> + <Compile Include="Apridisk\Write.cs" /> + <Compile Include="BlindWrite4\Constants.cs" /> + <Compile Include="BlindWrite4\Enums.cs" /> + <Compile Include="BlindWrite4\Identify.cs" /> + <Compile Include="BlindWrite4\Properties.cs" /> + <Compile Include="BlindWrite4\Read.cs" /> + <Compile Include="BlindWrite4\Structs.cs" /> + <Compile Include="BlindWrite5\Constants.cs" /> + <Compile Include="BlindWrite5\Enums.cs" /> + <Compile Include="BlindWrite5\Helpers.cs" /> + <Compile Include="BlindWrite5\Identify.cs" /> + <Compile Include="BlindWrite5\Properties.cs" /> + <Compile Include="BlindWrite5\Read.cs" /> + <Compile Include="BlindWrite5\Structs.cs" /> + <Compile Include="BLU\Constants.cs" /> + <Compile Include="BLU\Identify.cs" /> + <Compile Include="BLU\Properties.cs" /> + <Compile Include="BLU\Read.cs" /> + <Compile Include="BLU\Unsupported.cs" /> + <Compile Include="BLU\Write.cs" /> + <Compile Include="CDRDAO\Constants.cs" /> + <Compile Include="CDRDAO\Helpers.cs" /> + <Compile Include="CDRDAO\Identify.cs" /> + <Compile Include="CDRDAO\Properties.cs" /> + <Compile Include="CDRDAO\Read.cs" /> + <Compile Include="CDRDAO\Structs.cs" /> + <Compile Include="CDRDAO\Unsupported.cs" /> + <Compile Include="CDRDAO\Write.cs" /> + <Compile Include="CDRWin\Constants.cs" /> + <Compile Include="CDRWin\Helpers.cs" /> + <Compile Include="CDRWin\Identify.cs" /> + <Compile Include="CDRWin\Properties.cs" /> + <Compile Include="CDRWin\Read.cs" /> + <Compile Include="CDRWin\Structs.cs" /> + <Compile Include="CDRWin\Unsupported.cs" /> + <Compile Include="CDRWin\Write.cs" /> + <Compile Include="CHD\Constants.cs" /> + <Compile Include="CHD\Enums.cs" /> + <Compile Include="CHD\Helpers.cs" /> + <Compile Include="CHD\Identify.cs" /> + <Compile Include="CHD\Properties.cs" /> + <Compile Include="CHD\Read.cs" /> + <Compile Include="CHD\Structs.cs" /> + <Compile Include="CisCopy\Enums.cs" /> + <Compile Include="CisCopy\Identify.cs" /> + <Compile Include="CisCopy\Properties.cs" /> + <Compile Include="CisCopy\Read.cs" /> + <Compile Include="CisCopy\Unsupported.cs" /> + <Compile Include="CisCopy\Write.cs" /> + <Compile Include="CloneCD\Constants.cs" /> + <Compile Include="CloneCD\Helpers.cs" /> + <Compile Include="CloneCD\Identify.cs" /> + <Compile Include="CloneCD\Properties.cs" /> + <Compile Include="CloneCD\Read.cs" /> + <Compile Include="CloneCD\Unsupported.cs" /> + <Compile Include="CloneCD\Write.cs" /> + <Compile Include="CopyQM\Constants.cs" /> + <Compile Include="CopyQM\Identify.cs" /> + <Compile Include="CopyQM\Properties.cs" /> + <Compile Include="CopyQM\Read.cs" /> + <Compile Include="CopyQM\Structs.cs" /> + <Compile Include="CopyQM\Unsupported.cs" /> + <Compile Include="CPCDSK\Constants.cs" /> + <Compile Include="CPCDSK\Helpers.cs" /> + <Compile Include="CPCDSK\Identify.cs" /> + <Compile Include="CPCDSK\Properties.cs" /> + <Compile Include="CPCDSK\Read.cs" /> + <Compile Include="CPCDSK\Structs.cs" /> + <Compile Include="CPCDSK\Unsupported.cs" /> + <Compile Include="D88\Constants.cs" /> + <Compile Include="D88\Enums.cs" /> + <Compile Include="D88\Identify.cs" /> + <Compile Include="D88\Properties.cs" /> + <Compile Include="D88\Read.cs" /> + <Compile Include="D88\Structs.cs" /> + <Compile Include="D88\Unsupported.cs" /> + <Compile Include="DART\Constants.cs" /> + <Compile Include="DART\Identify.cs" /> + <Compile Include="DART\Properties.cs" /> + <Compile Include="DART\Read.cs" /> + <Compile Include="DART\Structs.cs" /> + <Compile Include="DART\Unsupported.cs" /> + <Compile Include="DIM\Constants.cs" /> + <Compile Include="DIM\Enums.cs" /> + <Compile Include="DIM\Identify.cs" /> + <Compile Include="DIM\Properties.cs" /> + <Compile Include="DIM\Read.cs" /> + <Compile Include="DIM\Unsupported.cs" /> + <Compile Include="DiscFerret\Constants.cs" /> + <Compile Include="DiscFerret\DiscFerret.cs" /> + <Compile Include="DiscFerret\Identify.cs" /> + <Compile Include="DiscFerret\Properties.cs" /> + <Compile Include="DiscFerret\Read.cs" /> + <Compile Include="DiscFerret\Structs.cs" /> + <Compile Include="DiscFerret\Unsupported.cs" /> + <Compile Include="DiscImageChef\ClauniaSubchannelTransform.cs" /> + <Compile Include="DiscImageChef\Constants.cs" /> + <Compile Include="DiscImageChef\DiscImageChef.cs" /> + <Compile Include="DiscImageChef\Enums.cs" /> + <Compile Include="DiscImageChef\Helpers.cs" /> + <Compile Include="DiscImageChef\Identify.cs" /> + <Compile Include="DiscImageChef\Properties.cs" /> + <Compile Include="DiscImageChef\Read.cs" /> + <Compile Include="DiscImageChef\Structs.cs" /> + <Compile Include="DiscImageChef\Verify.cs" /> + <Compile Include="DiscImageChef\Write.cs" /> + <Compile Include="DiscJuggler\Helpers.cs" /> + <Compile Include="DiscJuggler\Identify.cs" /> + <Compile Include="DiscJuggler\Properties.cs" /> + <Compile Include="DiscJuggler\Read.cs" /> + <Compile Include="DiskCopy42\Constants.cs" /> + <Compile Include="DiskCopy42\Helpers.cs" /> + <Compile Include="DiskCopy42\Identify.cs" /> + <Compile Include="DiskCopy42\Properties.cs" /> + <Compile Include="DiskCopy42\Read.cs" /> + <Compile Include="DiskCopy42\Structs.cs" /> + <Compile Include="DiskCopy42\Unsupported.cs" /> + <Compile Include="DiskCopy42\Write.cs" /> + <Compile Include="DriDiskCopy\Constants.cs" /> + <Compile Include="DriDiskCopy\Enums.cs" /> + <Compile Include="DriDiskCopy\Identify.cs" /> + <Compile Include="DriDiskCopy\Properties.cs" /> + <Compile Include="DriDiskCopy\Read.cs" /> + <Compile Include="DriDiskCopy\Structs.cs" /> + <Compile Include="DriDiskCopy\Unsupported.cs" /> + <Compile Include="DriDiskCopy\Write.cs" /> + <Compile Include="GDI\Constants.cs" /> + <Compile Include="GDI\Identify.cs" /> + <Compile Include="GDI\Properties.cs" /> + <Compile Include="GDI\Read.cs" /> + <Compile Include="GDI\Structs.cs" /> + <Compile Include="GDI\Unsupported.cs" /> + <Compile Include="HDCopy\HDCopy.cs" /> + <Compile Include="HDCopy\Helpers.cs" /> + <Compile Include="HDCopy\Identify.cs" /> + <Compile Include="HDCopy\Properties.cs" /> + <Compile Include="HDCopy\Read.cs" /> + <Compile Include="HDCopy\Structs.cs" /> + <Compile Include="HDCopy\Unsupported.cs" /> + <Compile Include="IMD\Constants.cs" /> + <Compile Include="IMD\Enums.cs" /> + <Compile Include="IMD\Identify.cs" /> + <Compile Include="IMD\Properties.cs" /> + <Compile Include="IMD\Read.cs" /> + <Compile Include="IMD\Unsupported.cs" /> + <Compile Include="KryoFlux\Constants.cs" /> + <Compile Include="KryoFlux\Enums.cs" /> + <Compile Include="KryoFlux\Identify.cs" /> + <Compile Include="KryoFlux\KryoFlux.cs" /> + <Compile Include="Apple2MG\Apple2MG.cs" /> + <Compile Include="CDRWin\CDRWin.cs" /> + <Compile Include="DiskCopy42\DiskCopy42.cs" /> + <Compile Include="KryoFlux\Properties.cs" /> + <Compile Include="KryoFlux\Read.cs" /> + <Compile Include="KryoFlux\Structs.cs" /> + <Compile Include="KryoFlux\Unsupported.cs" /> + <Compile Include="MaxiDisk\Enums.cs" /> + <Compile Include="MaxiDisk\Identify.cs" /> + <Compile Include="MaxiDisk\Properties.cs" /> + <Compile Include="MaxiDisk\Read.cs" /> + <Compile Include="MaxiDisk\Structs.cs" /> + <Compile Include="MaxiDisk\Unsupported.cs" /> + <Compile Include="MaxiDisk\Write.cs" /> + <Compile Include="NDIF\Constants.cs" /> + <Compile Include="NDIF\Identify.cs" /> + <Compile Include="NDIF\Properties.cs" /> + <Compile Include="NDIF\Read.cs" /> + <Compile Include="NDIF\Structs.cs" /> + <Compile Include="NDIF\Unsupported.cs" /> + <Compile Include="Nero\Constants.cs" /> + <Compile Include="Nero\Enums.cs" /> + <Compile Include="Nero\Helpers.cs" /> + <Compile Include="Nero\Identify.cs" /> + <Compile Include="Nero\Nero.cs" /> + <Compile Include="Nero\Properties.cs" /> + <Compile Include="Nero\Read.cs" /> + <Compile Include="Nero\Structs.cs" /> + <Compile Include="NHDr0\Constants.cs" /> + <Compile Include="NHDr0\Identify.cs" /> + <Compile Include="NHDr0\Properties.cs" /> + <Compile Include="NHDr0\Read.cs" /> + <Compile Include="NHDr0\Structs.cs" /> + <Compile Include="NHDr0\Unsupported.cs" /> + <Compile Include="NHDr0\Write.cs" /> + <Compile Include="Parallels\Constants.cs" /> + <Compile Include="Parallels\Identify.cs" /> + <Compile Include="Parallels\Properties.cs" /> + <Compile Include="Parallels\Read.cs" /> + <Compile Include="Parallels\Structs.cs" /> + <Compile Include="Parallels\Unsupported.cs" /> + <Compile Include="Parallels\Write.cs" /> + <Compile Include="PartClone\Constants.cs" /> + <Compile Include="PartClone\Helpers.cs" /> + <Compile Include="PartClone\Identify.cs" /> + <Compile Include="PartClone\Properties.cs" /> + <Compile Include="PartClone\Read.cs" /> + <Compile Include="PartClone\Structs.cs" /> + <Compile Include="PartClone\Unsupported.cs" /> + <Compile Include="Partimage\Constants.cs" /> + <Compile Include="Partimage\Enums.cs" /> + <Compile Include="Partimage\Helpers.cs" /> + <Compile Include="Partimage\Identify.cs" /> + <Compile Include="Partimage\Properties.cs" /> + <Compile Include="Partimage\Read.cs" /> + <Compile Include="Partimage\Structs.cs" /> + <Compile Include="Partimage\Unsupported.cs" /> + <Compile Include="QCOW2\Constants.cs" /> + <Compile Include="QCOW2\Identify.cs" /> + <Compile Include="QCOW2\Properties.cs" /> + <Compile Include="QCOW2\Read.cs" /> + <Compile Include="QCOW2\Structs.cs" /> + <Compile Include="QCOW2\Unsupported.cs" /> + <Compile Include="QCOW2\Write.cs" /> + <Compile Include="QCOW\Constants.cs" /> + <Compile Include="QCOW\Identify.cs" /> + <Compile Include="QCOW\Properties.cs" /> + <Compile Include="QCOW\Read.cs" /> + <Compile Include="QCOW\Structs.cs" /> + <Compile Include="QCOW\Unsupported.cs" /> + <Compile Include="QCOW\Write.cs" /> + <Compile Include="QED\Constants.cs" /> + <Compile Include="QED\Enums.cs" /> + <Compile Include="QED\Helpers.cs" /> + <Compile Include="QED\Identify.cs" /> + <Compile Include="QED\Properties.cs" /> + <Compile Include="QED\Read.cs" /> + <Compile Include="QED\Structs.cs" /> + <Compile Include="QED\Unsupported.cs" /> + <Compile Include="QED\Write.cs" /> + <Compile Include="RayDIM\Constants.cs" /> + <Compile Include="RayDIM\Enums.cs" /> + <Compile Include="RayDIM\Identify.cs" /> + <Compile Include="RayDIM\Properties.cs" /> + <Compile Include="RayDIM\Read.cs" /> + <Compile Include="RayDIM\Structs.cs" /> + <Compile Include="RayDIM\Unsupported.cs" /> + <Compile Include="RayDIM\Write.cs" /> <Compile Include="Register.cs" /> - <Compile Include="SuperCardPro.cs" /> - <Compile Include="TeleDisk.cs" /> - <Compile Include="VHD.cs" /> - <Compile Include="ZZZRawImage.cs" /> - <Compile Include="CDRDAO.cs" /> - <Compile Include="GDI.cs" /> - <Compile Include="BLU.cs" /> - <Compile Include="CopyQM.cs" /> - <Compile Include="Alcohol120.cs" /> - <Compile Include="BlindWrite4.cs" /> - <Compile Include="BlindWrite5.cs" /> - <Compile Include="DIM.cs" /> - <Compile Include="CPCDSK.cs" /> - <Compile Include="QCOW.cs" /> - <Compile Include="QED.cs" /> - <Compile Include="QCOW2.cs" /> - <Compile Include="Parallels.cs" /> - <Compile Include="VDI.cs" /> - <Compile Include="VHDX.cs" /> - <Compile Include="VMware.cs" /> - <Compile Include="UDIF.cs" /> - <Compile Include="NDIF.cs" /> - <Compile Include="DART.cs" /> - <Compile Include="CHD.cs" /> - <Compile Include="AppleNIB.cs" /> - <Compile Include="CloneCD.cs" /> - <Compile Include="DiscJuggler.cs" /> - <Compile Include="SaveDskF.cs" /> - <Compile Include="AppleDOS.cs" /> - <Compile Include="D88.cs" /> - <Compile Include="Anex86.cs" /> - <Compile Include="T98.cs" /> - <Compile Include="NHDr0.cs" /> - <Compile Include="Virtual98.cs" /> - <Compile Include="IMD.cs" /> - <Compile Include="CisCopy.cs" /> - <Compile Include="DriDiskCopy.cs" /> - <Compile Include="RsIde.cs" /> - <Compile Include="UkvFdi.cs" /> - <Compile Include="PartClone.cs" /> - <Compile Include="Partimage.cs" /> - <Compile Include="Apridisk.cs" /> - <Compile Include="MaxiDisk.cs" /> - <Compile Include="RayDIM.cs" /> + <Compile Include="RsIde\Constants.cs" /> + <Compile Include="RsIde\Enums.cs" /> + <Compile Include="RsIde\Helpers.cs" /> + <Compile Include="RsIde\Identify.cs" /> + <Compile Include="RsIde\Properties.cs" /> + <Compile Include="RsIde\Read.cs" /> + <Compile Include="RsIde\Structs.cs" /> + <Compile Include="RsIde\Unsupported.cs" /> + <Compile Include="RsIde\Write.cs" /> + <Compile Include="SaveDskF\Constants.cs" /> + <Compile Include="SaveDskF\Identify.cs" /> + <Compile Include="SaveDskF\Properties.cs" /> + <Compile Include="SaveDskF\Read.cs" /> + <Compile Include="SaveDskF\Structs.cs" /> + <Compile Include="SaveDskF\Unsupported.cs" /> + <Compile Include="SaveDskF\Write.cs" /> + <Compile Include="SuperCardPro\Constants.cs" /> + <Compile Include="SuperCardPro\Enums.cs" /> + <Compile Include="SuperCardPro\Helpers.cs" /> + <Compile Include="SuperCardPro\Identify.cs" /> + <Compile Include="SuperCardPro\Properties.cs" /> + <Compile Include="SuperCardPro\Read.cs" /> + <Compile Include="SuperCardPro\Structs.cs" /> + <Compile Include="SuperCardPro\SuperCardPro.cs" /> + <Compile Include="SuperCardPro\Unsupported.cs" /> + <Compile Include="T98\Identify.cs" /> + <Compile Include="T98\Properties.cs" /> + <Compile Include="T98\Read.cs" /> + <Compile Include="T98\Unsupported.cs" /> + <Compile Include="T98\Write.cs" /> + <Compile Include="TeleDisk\Constants.cs" /> + <Compile Include="TeleDisk\Helpers.cs" /> + <Compile Include="TeleDisk\Identify.cs" /> + <Compile Include="TeleDisk\Properties.cs" /> + <Compile Include="TeleDisk\Read.cs" /> + <Compile Include="TeleDisk\Structs.cs" /> + <Compile Include="TeleDisk\TeleDisk.cs" /> + <Compile Include="TeleDisk\Unsupported.cs" /> + <Compile Include="UDIF\Constants.cs" /> + <Compile Include="UDIF\Identify.cs" /> + <Compile Include="UDIF\Properties.cs" /> + <Compile Include="UDIF\Read.cs" /> + <Compile Include="UDIF\Structs.cs" /> + <Compile Include="UDIF\Unsupported.cs" /> + <Compile Include="UDIF\Write.cs" /> + <Compile Include="UkvFdi\Constants.cs" /> + <Compile Include="UkvFdi\Enums.cs" /> + <Compile Include="UkvFdi\Helpers.cs" /> + <Compile Include="UkvFdi\Identify.cs" /> + <Compile Include="UkvFdi\Properties.cs" /> + <Compile Include="UkvFdi\Read.cs" /> + <Compile Include="UkvFdi\Structs.cs" /> + <Compile Include="UkvFdi\Unsupported.cs" /> + <Compile Include="VDI\Constants.cs" /> + <Compile Include="VDI\Enums.cs" /> + <Compile Include="VDI\Identify.cs" /> + <Compile Include="VDI\Properties.cs" /> + <Compile Include="VDI\Read.cs" /> + <Compile Include="VDI\Structs.cs" /> + <Compile Include="VDI\Unsupported.cs" /> + <Compile Include="VDI\Write.cs" /> + <Compile Include="VHDX\Constants.cs" /> + <Compile Include="VHDX\Helpers.cs" /> + <Compile Include="VHDX\Identify.cs" /> + <Compile Include="VHDX\Properties.cs" /> + <Compile Include="VHDX\Read.cs" /> + <Compile Include="VHDX\Structs.cs" /> + <Compile Include="VHDX\Unsupported.cs" /> + <Compile Include="VHD\Constants.cs" /> + <Compile Include="VHD\Helpers.cs" /> + <Compile Include="VHD\Identify.cs" /> + <Compile Include="VHD\Properties.cs" /> + <Compile Include="VHD\Read.cs" /> + <Compile Include="VHD\Structs.cs" /> + <Compile Include="VHD\Unsupported.cs" /> + <Compile Include="VHD\VHD.cs" /> + <Compile Include="VHD\Write.cs" /> + <Compile Include="Virtual98\Constants.cs" /> + <Compile Include="Virtual98\Identify.cs" /> + <Compile Include="Virtual98\Properties.cs" /> + <Compile Include="Virtual98\Read.cs" /> + <Compile Include="Virtual98\Structs.cs" /> + <Compile Include="Virtual98\Unsupported.cs" /> + <Compile Include="Virtual98\Write.cs" /> + <Compile Include="VMware\Constants.cs" /> + <Compile Include="VMware\Identify.cs" /> + <Compile Include="VMware\Properties.cs" /> + <Compile Include="VMware\Read.cs" /> + <Compile Include="VMware\Structs.cs" /> + <Compile Include="VMware\Unsupported.cs" /> + <Compile Include="VMware\Write.cs" /> + <Compile Include="ZZZRawImage\Constants.cs" /> + <Compile Include="ZZZRawImage\Helpers.cs" /> + <Compile Include="ZZZRawImage\Identify.cs" /> + <Compile Include="ZZZRawImage\Properties.cs" /> + <Compile Include="ZZZRawImage\Read.cs" /> + <Compile Include="ZZZRawImage\Write.cs" /> + <Compile Include="ZZZRawImage\ZZZRawImage.cs" /> + <Compile Include="CDRDAO\CDRDAO.cs" /> + <Compile Include="GDI\GDI.cs" /> + <Compile Include="BLU\BLU.cs" /> + <Compile Include="CopyQM\CopyQM.cs" /> + <Compile Include="BlindWrite4\BlindWrite4.cs" /> + <Compile Include="BlindWrite5\BlindWrite5.cs" /> + <Compile Include="DIM\DIM.cs" /> + <Compile Include="CPCDSK\CPCDSK.cs" /> + <Compile Include="QCOW\QCOW.cs" /> + <Compile Include="QED\QED.cs" /> + <Compile Include="QCOW2\QCOW2.cs" /> + <Compile Include="Parallels\Parallels.cs" /> + <Compile Include="VDI\VDI.cs" /> + <Compile Include="VHDX\VHDX.cs" /> + <Compile Include="VMware\VMware.cs" /> + <Compile Include="UDIF\UDIF.cs" /> + <Compile Include="NDIF\NDIF.cs" /> + <Compile Include="DART\DART.cs" /> + <Compile Include="CHD\CHD.cs" /> + <Compile Include="AppleNIB\AppleNIB.cs" /> + <Compile Include="CloneCD\CloneCD.cs" /> + <Compile Include="DiscJuggler\DiscJuggler.cs" /> + <Compile Include="SaveDskF\SaveDskF.cs" /> + <Compile Include="AppleDOS\AppleDOS.cs" /> + <Compile Include="D88\D88.cs" /> + <Compile Include="T98\T98.cs" /> + <Compile Include="NHDr0\NHDr0.cs" /> + <Compile Include="Virtual98\Virtual98.cs" /> + <Compile Include="IMD\IMD.cs" /> + <Compile Include="CisCopy\CisCopy.cs" /> + <Compile Include="DriDiskCopy\DriDiskCopy.cs" /> + <Compile Include="RsIde\RsIde.cs" /> + <Compile Include="UkvFdi\UkvFdi.cs" /> + <Compile Include="PartClone\PartClone.cs" /> + <Compile Include="Partimage\Partimage.cs" /> + <Compile Include="Apridisk\Apridisk.cs" /> + <Compile Include="MaxiDisk\MaxiDisk.cs" /> + <Compile Include="RayDIM\RayDIM.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\DiscImageChef.CommonTypes\DiscImageChef.CommonTypes.csproj"> @@ -146,8 +513,6 @@ <Link>LICENSE.LGPL</Link> </EmbeddedResource> </ItemGroup> - <ItemGroup> - </ItemGroup> <ProjectExtensions> <MonoDevelop> <Properties> diff --git a/DiscImageChef.DiscImages/DiscImageChef.cs b/DiscImageChef.DiscImages/DiscImageChef.cs deleted file mode 100644 index 5ff8ed479..000000000 --- a/DiscImageChef.DiscImages/DiscImageChef.cs +++ /dev/null @@ -1,5504 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : DiscImageChef.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages DiscImageChef format disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -/* - The idea of the format is being able to easily store, retrieve, and access any data that can be read from media. - - At the start of a file there's a header that contains a format version, application creator name, and a pointer to - the index. - - The index points to one or several DeDuplication Tables, or media tag blocks. - - A deduplication table is a table of offsets to blocks and sectors inside blocks. Each entry equals to an LBA and points - to a byte offset in the file shift left to the number of sectors contained in a block, plus the number of sector inside - the block. - Each block must contain sectors of equal size, but that size can be different between blocks. - The deduplication table should be stored decompressed if its size is too big to be stored on-memory. This is chosen at - creation time but it is a good idea to set the limit to 256MiB (this allows for a device of 33 million sectors, - 17Gb at 512 bps, 68Gb at 2048 bps and 137Gb at 4096 bps). - - Sector tags that are simply too small to be deduplicated are contained in a single block pointed by the index (e.g. - Apple GCR sector tags). - - Optical disks contain a track block that describes the tracks. - TODO: Streaming tapes contain a file block that describes the files and an optional partition block that describes the tape - partitions. - - There are also blocks for image metadata, contents metadata and dump hardware information. - - A differencing image will have all the metadata and deduplication tables, but the entries in these ones will be set to - 0 if the block is stored in the parent image. TODO: This is not yet implemented. - - Also because the file becomes useless without the index and deduplication table, each can be stored twice. In case of - the index it should just be searched for. In case of deduplication tables, both copies should be indexed. - - Finally, writing new data to an existing image is just Copy-On-Write. Create a new block with the modified data, change - the pointer in the corresponding deduplication table. - - P.S.: Data Position Measurement is doable, as soon as I know how to do it. - P.S.2: Support for floppy image containg bitslices and/or fluxes will be added soon. -*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Text; -using System.Xml; -using System.Xml.Serialization; -using CUETools.Codecs; -using CUETools.Codecs.FLAKE; -using DiscImageChef.Checksums; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using DiscImageChef.Decoders; -using DiscImageChef.Decoders.ATA; -using DiscImageChef.Decoders.SCSI; -using DiscImageChef.Decoders.SecureDigital; -using Schemas; -using SharpCompress.Compressors.LZMA; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; -using VendorString = DiscImageChef.Decoders.SecureDigital.VendorString; - -namespace DiscImageChef.DiscImages -{ - public class DiscImageChef : IWritableImage - { - /// <summary>Magic identidier = "DICMFMT".</summary> - const ulong DIC_MAGIC = 0x544D52464D434944; - /// <summary> - /// Image format version. A change in this number indicates an incompatible change to the format that - /// prevents older implementations from reading it correctly, if at all. - /// </summary> - const byte DICF_VERSION = 1; - /// <summary>Maximum read cache size, 256MiB.</summary> - const uint MAX_CACHE_SIZE = 256 * 1024 * 1024; - /// <summary>Size in bytes of LZMA properties.</summary> - const int LZMA_PROPERTIES_LENGTH = 5; - /// <summary>Maximum number of entries for the DDT cache.</summary> - const int MAX_DDT_ENTRY_CACHE = 16000000; - /// <summary>How many samples are contained in a RedBook sector.</summary> - const int SAMPLES_PER_SECTOR = 588; - /// <summary>Maximum number of samples for a FLAC block. Bigger than 4608 gives no benefit.</summary> - const int MAX_FLAKE_BLOCK = 4608; - /// <summary> - /// Minimum number of samples for a FLAC block. <see cref="CUETools.Codecs.FLAKE" /> does not support it to be - /// smaller than 256. - /// </summary> - const int MIN_FLAKE_BLOCK = 256; - bool alreadyWrittenZero; - - /// <summary>Cache of uncompressed blocks.</summary> - Dictionary<ulong, byte[]> blockCache; - /// <summary>Cache of block headers.</summary> - Dictionary<ulong, BlockHeader> blockHeaderCache; - /// <summary>Stream used for writing blocks.</summary> - MemoryStream blockStream; - /// <summary>Provides checksum for deduplication of sectors.</summary> - SHA256 checksumProvider; - /// <summary>Provides CRC64.</summary> - Crc64Context crc64; - /// <summary>Header of the currently writing block.</summary> - BlockHeader currentBlockHeader; - /// <summary>Sector offset of writing position in currently writing block.</summary> - uint currentBlockOffset; - /// <summary>Current size in bytes of the block cache</summary> - uint currentCacheSize; - /// <summary>Cache of DDT entries.</summary> - Dictionary<ulong, ulong> ddtEntryCache; - MemoryStream decompressedStream; - bool deduplicate; - /// <summary>On-memory deduplication table indexed by checksum.</summary> - Dictionary<string, ulong> deduplicationTable; - /// <summary><see cref="CUETools.Codecs.FLAKE" /> writer.</summary> - FlakeWriter flakeWriter; - /// <summary><see cref="CUETools.Codecs.FLAKE" /> settings.</summary> - FlakeWriterSettings flakeWriterSettings; - /// <summary>Block with logical geometry.</summary> - GeometryBlock geometryBlock; - /// <summary>Image header.</summary> - DicHeader header; - /// <summary>Image information.</summary> - ImageInfo imageInfo; - /// <summary>Image data stream.</summary> - Stream imageStream; - /// <summary>Index.</summary> - List<IndexEntry> index; - /// <summary>If set to <c>true</c>, the DDT entries are in-memory.</summary> - bool inMemoryDdt; - ulong lastWrittenBlock; - /// <summary>LZMA stream.</summary> - LzmaStream lzmaBlockStream; - /// <summary>LZMA properties.</summary> - LzmaEncoderProperties lzmaEncoderProperties; - Md5Context md5Provider; - /// <summary>Cache of media tags.</summary> - Dictionary<MediaTagType, byte[]> mediaTags; - bool nocompress; - /// <summary>If DDT is on-disk, this is the image stream offset at which it starts.</summary> - long outMemoryDdtPosition; - bool rewinded; - /// <summary>Cache for data that prefixes the user data on a sector (e.g. sync).</summary> - byte[] sectorPrefix; - /// <summary>Cache for data that goes side by side with user data (e.g. CompactDisc subchannel).</summary> - byte[] sectorSubchannel; - /// <summary>Cache for data that suffixes the user data on a sector (e.g. edc, ecc).</summary> - byte[] sectorSuffix; - Sha1Context sha1Provider; - Sha256Context sha256Provider; - /// <summary>Shift for calculating number of sectors in a block.</summary> - byte shift; - SpamSumContext spamsumProvider; - /// <summary>Cache for bytes to write/rad on-disk.</summary> - byte[] structureBytes; - /// <summary>Cache for pointer for marshaling structures.</summary> - IntPtr structurePointer; - /// <summary>Cache of CompactDisc track's flags</summary> - Dictionary<byte, byte> trackFlags; - /// <summary>Cache of CompactDisc track's ISRC</summary> - Dictionary<byte, string> trackIsrcs; - /// <summary>In-memory deduplication table</summary> - ulong[] userDataDdt; - bool writingLong; - ulong writtenSectors; - - public DiscImageChef() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = "DiscImageChef", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - public string Name => "DiscImageChef format"; - public Guid Id => new Guid("49360069-1784-4A2F-B723-0C844D610B0A"); - public string Format => "DiscImageChef"; - public List<Partition> Partitions { get; private set; } - public List<Track> Tracks { get; private set; } - public List<Session> Sessions { get; private set; } - - public bool Identify(IFilter imageFilter) - { - imageStream = imageFilter.GetDataForkStream(); - imageStream.Seek(0, SeekOrigin.Begin); - - if(imageStream.Length < Marshal.SizeOf(header)) return false; - - header = new DicHeader(); - structureBytes = new byte[Marshal.SizeOf(header)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); - header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); - Marshal.FreeHGlobal(structurePointer); - - return header.identifier == DIC_MAGIC && header.imageMajorVersion <= DICF_VERSION; - } - - public bool Open(IFilter imageFilter) - { - imageStream = imageFilter.GetDataForkStream(); - imageStream.Seek(0, SeekOrigin.Begin); - - if(imageStream.Length < Marshal.SizeOf(header)) return false; - - header = new DicHeader(); - structureBytes = new byte[Marshal.SizeOf(header)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); - header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); - Marshal.FreeHGlobal(structurePointer); - - if(header.imageMajorVersion > DICF_VERSION) - throw new FeatureUnsupportedImageException($"Image version {header.imageMajorVersion} not recognized."); - - imageInfo.Application = header.application; - imageInfo.ApplicationVersion = $"{header.applicationMajorVersion}.{header.applicationMinorVersion}"; - imageInfo.Version = $"{header.imageMajorVersion}.{header.imageMinorVersion}"; - imageInfo.MediaType = header.mediaType; - - // Read the index header - imageStream.Position = (long)header.indexOffset; - IndexHeader idxHeader = new IndexHeader(); - structureBytes = new byte[Marshal.SizeOf(idxHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(idxHeader)); - idxHeader = (IndexHeader)Marshal.PtrToStructure(structurePointer, typeof(IndexHeader)); - Marshal.FreeHGlobal(structurePointer); - - if(idxHeader.identifier != BlockType.Index) throw new FeatureUnsupportedImageException("Index not found!"); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", - header.indexOffset, idxHeader.entries); - - // Fill in-memory index - index = new List<IndexEntry>(); - for(ushort i = 0; i < idxHeader.entries; i++) - { - IndexEntry entry = new IndexEntry(); - structureBytes = new byte[Marshal.SizeOf(entry)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(entry)); - entry = (IndexEntry)Marshal.PtrToStructure(structurePointer, typeof(IndexEntry)); - Marshal.FreeHGlobal(structurePointer); - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Block type {0} with data type {1} is indexed to be at {2}", entry.blockType, - entry.dataType, entry.offset); - index.Add(entry); - } - - imageInfo.ImageSize = 0; - - bool foundUserDataDdt = false; - mediaTags = new Dictionary<MediaTagType, byte[]>(); - foreach(IndexEntry entry in index) - { - imageStream.Position = (long)entry.offset; - switch(entry.blockType) - { - case BlockType.DataBlock: - // NOP block, skip - if(entry.dataType == DataType.NoData) break; - - imageStream.Position = (long)entry.offset; - - BlockHeader blockHeader = new BlockHeader(); - structureBytes = new byte[Marshal.SizeOf(blockHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); - blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); - Marshal.FreeHGlobal(structurePointer); - imageInfo.ImageSize += blockHeader.cmpLength; - - // Unused, skip - if(entry.dataType == DataType.UserData) - { - if(blockHeader.sectorSize > imageInfo.SectorSize) - imageInfo.SectorSize = blockHeader.sectorSize; - break; - } - - if(blockHeader.identifier != entry.blockType) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect identifier for data block at position {0}", - entry.offset); - break; - } - - if(blockHeader.type != entry.dataType) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Expected block with data type {0} at position {1} but found data type {2}", - entry.dataType, entry.offset, blockHeader.type); - break; - } - - byte[] data; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found data block type {0} at position {1}", entry.dataType, - entry.offset); - - // Decompress media tag - if(blockHeader.compression == CompressionType.Lzma || blockHeader.compression == - CompressionType.LzmaClauniaSubchannelTransform) - { - if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform && - entry.dataType != DataType.CdSectorSubchannel) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Invalid compression type {0} for block with data type {1}, continuing...", - blockHeader.compression, entry.dataType); - break; - } - - DateTime startDecompress = DateTime.Now; - byte[] compressedTag = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; - byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; - imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); - imageStream.Read(compressedTag, 0, compressedTag.Length); - MemoryStream compressedTagMs = new MemoryStream(compressedTag); - LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedTagMs); - data = new byte[blockHeader.length]; - lzmaBlock.Read(data, 0, (int)blockHeader.length); - lzmaBlock.Close(); - compressedTagMs.Close(); - - if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform) - data = ClauniaSubchannelUntransform(data); - - DateTime endDecompress = DateTime.Now; - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Took {0} seconds to decompress block", - (endDecompress - startDecompress).TotalSeconds); - } - else if(blockHeader.compression == CompressionType.None) - { - data = new byte[blockHeader.length]; - imageStream.Read(data, 0, (int)blockHeader.length); - } - else - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found unknown compression type {0}, continuing...", - (ushort)blockHeader.compression); - break; - } - - // Check CRC, if not correct, skip it - Crc64Context.Data(data, out byte[] blockCrc); - if(BitConverter.ToUInt64(blockCrc, 0) != blockHeader.crc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", - BitConverter.ToUInt64(blockCrc, 0), blockHeader.crc64); - break; - } - - // Check if it's not a media tag, but a sector tag, and fill the appropriate table then - switch(entry.dataType) - { - case DataType.CdSectorPrefix: - sectorPrefix = data; - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync); - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader); - break; - case DataType.CdSectorSuffix: - sectorSuffix = data; - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubHeader)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubHeader); - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEcc)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEcc); - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccP)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccP); - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccQ)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccQ); - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEdc)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEdc); - break; - case DataType.CdSectorSubchannel: - sectorSubchannel = data; - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel)) - imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel); - break; - case DataType.AppleProfileTag: - case DataType.AppleSonyTag: - case DataType.PriamDataTowerTag: - sectorSubchannel = data; - if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.AppleSectorTag)) - imageInfo.ReadableSectorTags.Add(SectorTagType.AppleSectorTag); - break; - default: - MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); - - if(mediaTags.ContainsKey(mediaTagType)) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Media tag type {0} duplicated, removing previous entry...", - mediaTagType); - - mediaTags.Remove(mediaTagType); - } - - mediaTags.Add(mediaTagType, data); - break; - } - - break; - case BlockType.DeDuplicationTable: - // Only user data deduplication tables are used right now - if(entry.dataType != DataType.UserData) break; - - DdtHeader ddtHeader = new DdtHeader(); - structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(ddtHeader)); - ddtHeader = (DdtHeader)Marshal.PtrToStructure(structurePointer, typeof(DdtHeader)); - Marshal.FreeHGlobal(structurePointer); - imageInfo.ImageSize += ddtHeader.cmpLength; - - if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; - - imageInfo.Sectors = ddtHeader.entries; - shift = ddtHeader.shift; - - // Check for DDT compression - switch(ddtHeader.compression) - { - case CompressionType.Lzma: - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Decompressing DDT..."); - DateTime ddtStart = DateTime.UtcNow; - byte[] compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; - byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; - imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); - imageStream.Read(compressedDdt, 0, compressedDdt.Length); - MemoryStream compressedDdtMs = new MemoryStream(compressedDdt); - LzmaStream lzmaDdt = new LzmaStream(lzmaProperties, compressedDdtMs); - byte[] decompressedDdt = new byte[ddtHeader.length]; - lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); - lzmaDdt.Close(); - compressedDdtMs.Close(); - userDataDdt = new ulong[ddtHeader.entries]; - for(ulong i = 0; i < ddtHeader.entries; i++) - userDataDdt[i] = BitConverter.ToUInt64(decompressedDdt, (int)(i * sizeof(ulong))); - DateTime ddtEnd = DateTime.UtcNow; - inMemoryDdt = true; - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Took {0} seconds to decompress DDT", - (ddtEnd - ddtStart).TotalSeconds); - break; - case CompressionType.None: - inMemoryDdt = false; - outMemoryDdtPosition = (long)entry.offset; - break; - default: - throw new - ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)ddtHeader.compression}"); - } - - foundUserDataDdt = true; - break; - // Logical geometry block. It doesn't have a CRC coz, well, it's not so important - case BlockType.GeometryBlock: - geometryBlock = new GeometryBlock(); - structureBytes = new byte[Marshal.SizeOf(geometryBlock)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(geometryBlock)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(geometryBlock)); - geometryBlock = (GeometryBlock)Marshal.PtrToStructure(structurePointer, typeof(GeometryBlock)); - Marshal.FreeHGlobal(structurePointer); - if(geometryBlock.identifier == BlockType.GeometryBlock) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Geometry set to {0} cylinders {1} heads {2} sectors per track", - geometryBlock.cylinders, geometryBlock.heads, - geometryBlock.sectorsPerTrack); - imageInfo.Cylinders = geometryBlock.cylinders; - imageInfo.Heads = geometryBlock.heads; - imageInfo.SectorsPerTrack = geometryBlock.sectorsPerTrack; - } - - break; - // Metadata block - case BlockType.MetadataBlock: - MetadataBlock metadataBlock = new MetadataBlock(); - structureBytes = new byte[Marshal.SizeOf(metadataBlock)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(metadataBlock)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(metadataBlock)); - metadataBlock = (MetadataBlock)Marshal.PtrToStructure(structurePointer, typeof(MetadataBlock)); - Marshal.FreeHGlobal(structurePointer); - - if(metadataBlock.identifier != entry.blockType) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect identifier for data block at position {0}", - entry.offset); - break; - } - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found metadata block at position {0}", - entry.offset); - - byte[] metadata = new byte[metadataBlock.blockSize]; - imageStream.Position = (long)entry.offset; - imageStream.Read(metadata, 0, metadata.Length); - - if(metadataBlock.mediaSequence > 0 && metadataBlock.lastMediaSequence > 0) - { - imageInfo.MediaSequence = metadataBlock.mediaSequence; - imageInfo.LastMediaSequence = metadataBlock.lastMediaSequence; - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Setting media sequence as {0} of {1}", imageInfo.MediaSequence, - imageInfo.LastMediaSequence); - } - - if(metadataBlock.creatorLength > 0 && - metadataBlock.creatorLength + metadataBlock.creatorOffset <= metadata.Length) - { - imageInfo.Creator = Encoding.Unicode.GetString(metadata, (int)metadataBlock.creatorOffset, - (int)(metadataBlock.creatorLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting creator: {0}", - imageInfo.Creator); - } - - if(metadataBlock.commentsOffset > 0 && - metadataBlock.commentsLength + metadataBlock.commentsOffset <= metadata.Length) - { - imageInfo.Comments = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.commentsOffset, - (int)(metadataBlock.commentsLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting comments: {0}", - imageInfo.Comments); - } - - if(metadataBlock.mediaTitleOffset > 0 && - metadataBlock.mediaTitleLength + metadataBlock.mediaTitleOffset <= metadata.Length) - { - imageInfo.MediaTitle = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaTitleOffset, - (int)(metadataBlock.mediaTitleLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media title: {0}", - imageInfo.MediaTitle); - } - - if(metadataBlock.mediaManufacturerOffset > 0 && - metadataBlock.mediaManufacturerLength + metadataBlock.mediaManufacturerOffset <= - metadata.Length) - { - imageInfo.MediaManufacturer = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaManufacturerOffset, - (int)(metadataBlock.mediaManufacturerLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media manufacturer: {0}", - imageInfo.MediaManufacturer); - } - - if(metadataBlock.mediaModelOffset > 0 && - metadataBlock.mediaModelLength + metadataBlock.mediaModelOffset <= metadata.Length) - { - imageInfo.MediaModel = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaModelOffset, - (int)(metadataBlock.mediaModelLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media model: {0}", - imageInfo.MediaModel); - } - - if(metadataBlock.mediaSerialNumberOffset > 0 && - metadataBlock.mediaSerialNumberLength + metadataBlock.mediaSerialNumberOffset <= - metadata.Length) - { - imageInfo.MediaSerialNumber = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaSerialNumberOffset, - (int)(metadataBlock.mediaSerialNumberLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media serial number: {0}", - imageInfo.MediaSerialNumber); - } - - if(metadataBlock.mediaBarcodeOffset > 0 && - metadataBlock.mediaBarcodeLength + metadataBlock.mediaBarcodeOffset <= metadata.Length) - { - imageInfo.MediaBarcode = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaBarcodeOffset, - (int)(metadataBlock.mediaBarcodeLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media barcode: {0}", - imageInfo.MediaBarcode); - } - - if(metadataBlock.mediaPartNumberOffset > 0 && - metadataBlock.mediaPartNumberLength + metadataBlock.mediaPartNumberOffset <= metadata.Length) - { - imageInfo.MediaPartNumber = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaPartNumberOffset, - (int)(metadataBlock.mediaPartNumberLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media part number: {0}", - imageInfo.MediaPartNumber); - } - - if(metadataBlock.driveManufacturerOffset > 0 && - metadataBlock.driveManufacturerLength + metadataBlock.driveManufacturerOffset <= - metadata.Length) - { - imageInfo.DriveManufacturer = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveManufacturerOffset, - (int)(metadataBlock.driveManufacturerLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive manufacturer: {0}", - imageInfo.DriveManufacturer); - } - - if(metadataBlock.driveModelOffset > 0 && - metadataBlock.driveModelLength + metadataBlock.driveModelOffset <= metadata.Length) - { - imageInfo.DriveModel = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveModelOffset, - (int)(metadataBlock.driveModelLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive model: {0}", - imageInfo.DriveModel); - } - - if(metadataBlock.driveSerialNumberOffset > 0 && - metadataBlock.driveSerialNumberLength + metadataBlock.driveSerialNumberOffset <= - metadata.Length) - { - imageInfo.DriveSerialNumber = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveSerialNumberOffset, - (int)(metadataBlock.driveSerialNumberLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive serial number: {0}", - imageInfo.DriveSerialNumber); - } - - if(metadataBlock.driveFirmwareRevisionOffset > 0 && metadataBlock.driveFirmwareRevisionLength + - metadataBlock.driveFirmwareRevisionOffset <= metadata.Length) - { - imageInfo.DriveFirmwareRevision = - Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveFirmwareRevisionOffset, - (int)(metadataBlock.driveFirmwareRevisionLength - 2)); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Setting drive firmware revision: {0}", - imageInfo.DriveFirmwareRevision); - } - - break; - // Optical disc tracks block - case BlockType.TracksBlock: - TracksHeader tracksHeader = new TracksHeader(); - structureBytes = new byte[Marshal.SizeOf(tracksHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(tracksHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(tracksHeader)); - tracksHeader = (TracksHeader)Marshal.PtrToStructure(structurePointer, typeof(TracksHeader)); - Marshal.FreeHGlobal(structurePointer); - if(tracksHeader.identifier != BlockType.TracksBlock) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect identifier for tracks block at position {0}", - entry.offset); - break; - } - - structureBytes = new byte[Marshal.SizeOf(typeof(TrackEntry)) * tracksHeader.entries]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - Crc64Context.Data(structureBytes, out byte[] trksCrc); - if(BitConverter.ToUInt64(trksCrc, 0) != tracksHeader.crc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", - BitConverter.ToUInt64(trksCrc, 0), tracksHeader.crc64); - break; - } - - imageStream.Position -= structureBytes.Length; - - Tracks = new List<Track>(); - trackFlags = new Dictionary<byte, byte>(); - trackIsrcs = new Dictionary<byte, string>(); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found {0} tracks at position {0}", - tracksHeader.entries, entry.offset); - - for(ushort i = 0; i < tracksHeader.entries; i++) - { - TrackEntry trackEntry = new TrackEntry(); - structureBytes = new byte[Marshal.SizeOf(trackEntry)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trackEntry)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(trackEntry)); - trackEntry = (TrackEntry)Marshal.PtrToStructure(structurePointer, typeof(TrackEntry)); - Marshal.FreeHGlobal(structurePointer); - - Tracks.Add(new Track - { - TrackSequence = trackEntry.sequence, - TrackType = trackEntry.type, - TrackStartSector = (ulong)trackEntry.start, - TrackEndSector = (ulong)trackEntry.end, - TrackPregap = (ulong)trackEntry.pregap, - TrackSession = trackEntry.session, - TrackFile = imageFilter.GetFilename(), - TrackFileType = "BINARY", - TrackFilter = imageFilter - }); - - trackFlags.Add(trackEntry.sequence, trackEntry.flags); - trackIsrcs.Add(trackEntry.sequence, trackEntry.isrc); - } - - imageInfo.HasPartitions = true; - imageInfo.HasSessions = true; - break; - // CICM XML metadata block - case BlockType.CicmBlock: - CicmMetadataBlock cicmBlock = new CicmMetadataBlock(); - structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(cicmBlock)); - cicmBlock = (CicmMetadataBlock)Marshal.PtrToStructure(structurePointer, - typeof(CicmMetadataBlock)); - Marshal.FreeHGlobal(structurePointer); - if(cicmBlock.identifier != BlockType.CicmBlock) break; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found CICM XML metadata block at position {0}", entry.offset); - - byte[] cicmBytes = new byte[cicmBlock.length]; - imageStream.Read(cicmBytes, 0, cicmBytes.Length); - MemoryStream cicmMs = new MemoryStream(cicmBytes); - XmlSerializer cicmXs = new XmlSerializer(typeof(CICMMetadataType)); - try - { - StreamReader sr = new StreamReader(cicmMs); - CicmMetadata = (CICMMetadataType)cicmXs.Deserialize(sr); - sr.Close(); - } - catch(XmlException ex) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Exception {0} processing CICM XML metadata block", ex.Message); - CicmMetadata = null; - } - - break; - // Dump hardware block - case BlockType.DumpHardwareBlock: - DumpHardwareHeader dumpBlock = new DumpHardwareHeader(); - structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpBlock)); - dumpBlock = (DumpHardwareHeader)Marshal.PtrToStructure(structurePointer, - typeof(DumpHardwareHeader)); - Marshal.FreeHGlobal(structurePointer); - if(dumpBlock.identifier != BlockType.DumpHardwareBlock) break; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found dump hardware block at position {0}", entry.offset); - - structureBytes = new byte[dumpBlock.length]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - Crc64Context.Data(structureBytes, out byte[] dumpCrc); - if(BitConverter.ToUInt64(dumpCrc, 0) != dumpBlock.crc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", - BitConverter.ToUInt64(dumpCrc, 0), dumpBlock.crc64); - break; - } - - imageStream.Position -= structureBytes.Length; - - DumpHardware = new List<DumpHardwareType>(); - - for(ushort i = 0; i < dumpBlock.entries; i++) - { - DumpHardwareEntry dumpEntry = new DumpHardwareEntry(); - structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpEntry)); - dumpEntry = (DumpHardwareEntry)Marshal.PtrToStructure(structurePointer, - typeof(DumpHardwareEntry)); - Marshal.FreeHGlobal(structurePointer); - - DumpHardwareType dump = new DumpHardwareType - { - Software = new SoftwareType(), - Extents = new ExtentType[dumpEntry.extents] - }; - - byte[] tmp; - - if(dumpEntry.manufacturerLength > 0) - { - tmp = new byte[dumpEntry.manufacturerLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Manufacturer = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.modelLength > 0) - { - tmp = new byte[dumpEntry.modelLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Model = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.revisionLength > 0) - { - tmp = new byte[dumpEntry.revisionLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Revision = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.firmwareLength > 0) - { - tmp = new byte[dumpEntry.firmwareLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Firmware = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.serialLength > 0) - { - tmp = new byte[dumpEntry.serialLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Serial = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.softwareNameLength > 0) - { - tmp = new byte[dumpEntry.softwareNameLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Software.Name = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.softwareVersionLength > 0) - { - tmp = new byte[dumpEntry.softwareVersionLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Software.Version = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.softwareOperatingSystemLength > 0) - { - tmp = new byte[dumpEntry.softwareOperatingSystemLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Software.OperatingSystem = Encoding.UTF8.GetString(tmp); - } - - tmp = new byte[16]; - for(uint j = 0; j < dumpEntry.extents; j++) - { - imageStream.Read(tmp, 0, tmp.Length); - dump.Extents[j] = new ExtentType - { - Start = BitConverter.ToUInt64(tmp, 0), - End = BitConverter.ToUInt64(tmp, 8) - }; - } - - dump.Extents = dump.Extents.OrderBy(t => t.Start).ToArray(); - if(dump.Extents.Length > 0) DumpHardware.Add(dump); - } - - if(DumpHardware.Count == 0) DumpHardware = null; - break; - } - } - - if(!foundUserDataDdt) throw new ImageNotSupportedException("Could not find user data deduplication table."); - - imageInfo.CreationTime = DateTime.FromFileTimeUtc(header.creationTime); - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Image created on {0}", imageInfo.CreationTime); - imageInfo.LastModificationTime = DateTime.FromFileTimeUtc(header.lastWrittenTime); - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Image last written on {0}", - imageInfo.LastModificationTime); - - if(geometryBlock.identifier != BlockType.GeometryBlock && imageInfo.XmlMediaType == XmlMediaType.BlockMedia) - { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - } - - imageInfo.XmlMediaType = GetXmlMediaType(header.mediaType); - imageInfo.ReadableMediaTags.AddRange(mediaTags.Keys); - - // Initialize caches - blockCache = new Dictionary<ulong, byte[]>(); - blockHeaderCache = new Dictionary<ulong, BlockHeader>(); - currentCacheSize = 0; - if(!inMemoryDdt) ddtEntryCache = new Dictionary<ulong, ulong>(); - - // Initialize tracks, sessions and partitions - if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) - { - if(Tracks == null || Tracks.Count == 0) - { - Tracks = new List<Track> - { - new Track - { - Indexes = new Dictionary<int, ulong>(), - TrackBytesPerSector = (int)imageInfo.SectorSize, - TrackEndSector = imageInfo.Sectors - 1, - TrackFile = imageFilter.GetFilename(), - TrackFileType = "BINARY", - TrackFilter = imageFilter, - TrackRawBytesPerSector = (int)imageInfo.SectorSize, - TrackSession = 1, - TrackSequence = 1, - TrackType = TrackType.Data - } - }; - - trackFlags = new Dictionary<byte, byte> {{1, (byte)CdFlags.DataTrack}}; - trackIsrcs = new Dictionary<byte, string>(); - } - - Sessions = new List<Session>(); - for(int i = 1; i <= Tracks.Max(t => t.TrackSession); i++) - Sessions.Add(new Session - { - SessionSequence = (ushort)i, - StartTrack = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackSequence), - EndTrack = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackSequence), - StartSector = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackStartSector), - EndSector = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackEndSector) - }); - - ulong currentTrackOffset = 0; - Partitions = new List<Partition>(); - foreach(Track track in Tracks.OrderBy(t => t.TrackStartSector)) - { - Partitions.Add(new Partition - { - Sequence = track.TrackSequence, - Type = track.TrackType.ToString(), - Name = $"Track {track.TrackSequence}", - Offset = currentTrackOffset, - Start = track.TrackStartSector, - Size = (track.TrackEndSector - track.TrackStartSector + 1) * - (ulong)track.TrackBytesPerSector, - Length = track.TrackEndSector - track.TrackStartSector + 1, - Scheme = "Optical disc track" - }); - currentTrackOffset += (track.TrackEndSector - track.TrackStartSector + 1) * - (ulong)track.TrackBytesPerSector; - } - - Track[] tracks = Tracks.ToArray(); - for(int i = 0; i < tracks.Length; i++) - { - byte[] sector = ReadSector(tracks[i].TrackStartSector); - tracks[i].TrackBytesPerSector = sector.Length; - tracks[i].TrackRawBytesPerSector = - sectorPrefix != null && sectorSuffix != null ? 2352 : sector.Length; - - if(sectorSubchannel == null) continue; - - tracks[i].TrackSubchannelFile = tracks[i].TrackFile; - tracks[i].TrackSubchannelFilter = tracks[i].TrackFilter; - tracks[i].TrackSubchannelType = TrackSubchannelType.Raw; - } - - Tracks = tracks.ToList(); - } - else - { - Tracks = null; - Sessions = null; - Partitions = null; - } - - SetMetadataFromTags(); - - return true; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - if(mediaTags.TryGetValue(tag, out byte[] data)) return data; - - throw new FeatureNotPresentImageException("Requested tag is not present in image"); - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - ulong ddtEntry = GetDdtEntry(sectorAddress); - uint offsetMask = (uint)((1 << shift) - 1); - ulong offset = ddtEntry & offsetMask; - ulong blockOffset = ddtEntry >> shift; - - // Partially written image... as we can't know the real sector size just assume it's common :/ - if(ddtEntry == 0) return new byte[imageInfo.SectorSize]; - - byte[] sector; - - // Check if block is cached - if(blockCache.TryGetValue(blockOffset, out byte[] block) && - blockHeaderCache.TryGetValue(blockOffset, out BlockHeader blockHeader)) - { - sector = new byte[blockHeader.sectorSize]; - Array.Copy(block, (long)(offset * blockHeader.sectorSize), sector, 0, blockHeader.sectorSize); - return sector; - } - - // Read block header - imageStream.Position = (long)blockOffset; - blockHeader = new BlockHeader(); - structureBytes = new byte[Marshal.SizeOf(blockHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); - blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); - Marshal.FreeHGlobal(structurePointer); - - // Decompress block - switch(blockHeader.compression) - { - case CompressionType.None: - block = new byte[blockHeader.length]; - imageStream.Read(block, 0, (int)blockHeader.length); - break; - case CompressionType.Lzma: - byte[] compressedBlock = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; - byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; - imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); - imageStream.Read(compressedBlock, 0, compressedBlock.Length); - MemoryStream compressedBlockMs = new MemoryStream(compressedBlock); - LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedBlockMs); - block = new byte[blockHeader.length]; - lzmaBlock.Read(block, 0, (int)blockHeader.length); - lzmaBlock.Close(); - compressedBlockMs.Close(); - break; - case CompressionType.Flac: - byte[] flacBlock = new byte[blockHeader.cmpLength]; - imageStream.Read(flacBlock, 0, flacBlock.Length); - MemoryStream flacMs = new MemoryStream(flacBlock); - FlakeReader flakeReader = new FlakeReader("", flacMs); - block = new byte[blockHeader.length]; - int samples = (int)(block.Length / blockHeader.sectorSize * 588); - AudioBuffer audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, block, samples); - flakeReader.Read(audioBuffer, samples); - flakeReader.Close(); - flacMs.Close(); - break; - default: - throw new - ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)blockHeader.compression}"); - } - - // Check if cache needs to be emptied - if(currentCacheSize + blockHeader.length >= MAX_CACHE_SIZE) - { - currentCacheSize = 0; - blockHeaderCache = new Dictionary<ulong, BlockHeader>(); - blockCache = new Dictionary<ulong, byte[]>(); - } - - // Add block to cache - currentCacheSize += blockHeader.length; - blockHeaderCache.Add(blockOffset, blockHeader); - blockCache.Add(blockOffset, block); - - sector = new byte[blockHeader.sectorSize]; - Array.Copy(block, (long)(offset * blockHeader.sectorSize), sector, 0, blockHeader.sectorSize); - return sector; - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - return ReadSectorsTag(sectorAddress, 1, tag); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureNotPresentImageException("Feature not present in image"); - - Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); - if(trk.TrackSequence != track) - throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); - - return ReadSector(trk.TrackStartSector + sectorAddress); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureNotPresentImageException("Feature not present in image"); - - Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); - if(trk.TrackSequence != track) - throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); - - return ReadSectorTag(trk.TrackStartSector + sectorAddress, tag); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream ms = new MemoryStream(); - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - uint sectorOffset; - uint sectorSize; - uint sectorSkip; - byte[] dataSource; - - if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) - { - Track trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && - sectorAddress <= t.TrackEndSector); - if(trk.TrackSequence == 0) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - "Can't found track containing requested sector"); - - if(trk.TrackType == TrackType.Data) - throw new ArgumentException("Unsupported tag requested", nameof(tag)); - - switch(tag) - { - case SectorTagType.CdSectorEcc: - case SectorTagType.CdSectorEccP: - case SectorTagType.CdSectorEccQ: - case SectorTagType.CdSectorEdc: - case SectorTagType.CdSectorHeader: - case SectorTagType.CdSectorSubchannel: - case SectorTagType.CdSectorSubHeader: - case SectorTagType.CdSectorSync: break; - case SectorTagType.CdTrackFlags: - return trackFlags.TryGetValue((byte)trk.TrackSequence, out byte flags) ? new[] {flags} : null; - case SectorTagType.CdTrackIsrc: - return trackIsrcs.TryGetValue((byte)trk.TrackSequence, out string isrc) - ? Encoding.UTF8.GetBytes(isrc) - : null; - default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); - } - - switch(trk.TrackType) - { - case TrackType.CdMode1: - switch(tag) - { - case SectorTagType.CdSectorSync: - { - sectorOffset = 0; - sectorSize = 12; - sectorSkip = 4; - dataSource = sectorPrefix; - break; - } - case SectorTagType.CdSectorHeader: - { - sectorOffset = 12; - sectorSize = 4; - sectorSkip = 2336; - dataSource = sectorPrefix; - break; - } - case SectorTagType.CdSectorSubHeader: - throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); - case SectorTagType.CdSectorEcc: - { - sectorOffset = 12; - sectorSize = 276; - sectorSkip = 0; - dataSource = sectorSuffix; - break; - } - case SectorTagType.CdSectorEccP: - { - sectorOffset = 12; - sectorSize = 172; - sectorSkip = 104; - dataSource = sectorSuffix; - break; - } - case SectorTagType.CdSectorEccQ: - { - sectorOffset = 184; - sectorSize = 104; - sectorSkip = 0; - dataSource = sectorSuffix; - break; - } - case SectorTagType.CdSectorEdc: - { - sectorOffset = 0; - sectorSize = 4; - sectorSkip = 284; - dataSource = sectorSuffix; - break; - } - case SectorTagType.CdSectorSubchannel: - { - sectorOffset = 0; - sectorSize = 96; - sectorSkip = 0; - dataSource = sectorSubchannel; - break; - } - default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); - } - - break; - case TrackType.CdMode2Formless: - case TrackType.CdMode2Form1: - case TrackType.CdMode2Form2: - { - switch(tag) - { - case SectorTagType.CdSectorSync: - { - sectorOffset = 0; - sectorSize = 12; - sectorSkip = 4; - dataSource = sectorPrefix; - break; - } - case SectorTagType.CdSectorHeader: - { - sectorOffset = 12; - sectorSize = 4; - sectorSkip = 2336; - dataSource = sectorPrefix; - break; - } - // These could be implemented - case SectorTagType.CdSectorEcc: - case SectorTagType.CdSectorEccP: - case SectorTagType.CdSectorEccQ: - case SectorTagType.CdSectorSubHeader: - case SectorTagType.CdSectorEdc: - throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); - case SectorTagType.CdSectorSubchannel: - { - sectorOffset = 0; - sectorSize = 96; - sectorSkip = 0; - dataSource = sectorSubchannel; - break; - } - default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); - } - - break; - } - case TrackType.Audio: - { - switch(tag) - { - case SectorTagType.CdSectorSubchannel: - { - sectorOffset = 0; - sectorSize = 96; - sectorSkip = 0; - dataSource = sectorSubchannel; - break; - } - default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); - } - - break; - } - default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); - } - } - else throw new FeatureNotPresentImageException("Feature not present in image"); - - if(dataSource == null) throw new ArgumentException("Unsupported tag requested", nameof(tag)); - - byte[] data = new byte[sectorSize * length]; - - if(sectorOffset == 0 && sectorSkip == 0) - { - Array.Copy(dataSource, (long)(sectorAddress * sectorSize), data, 0, length * sectorSize); - return data; - } - - for(int i = 0; i < length; i++) - Array.Copy(dataSource, (long)(sectorAddress * (sectorOffset + sectorSize + sectorSkip)), data, - i * sectorSize, sectorSize); - - return data; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureNotPresentImageException("Feature not present in image"); - - Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); - if(trk.TrackSequence != track) - throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); - - if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) - throw new ArgumentOutOfRangeException(nameof(length), - $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); - - return ReadSectors(trk.TrackStartSector + sectorAddress, length); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureNotPresentImageException("Feature not present in image"); - - Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); - if(trk.TrackSequence != track) - throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); - - if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) - throw new ArgumentOutOfRangeException(nameof(length), - $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); - - return ReadSectorsTag(trk.TrackStartSector + sectorAddress, length, tag); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - switch(imageInfo.XmlMediaType) - { - case XmlMediaType.OpticalDisc: - Track trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && - sectorAddress <= t.TrackEndSector); - if(trk.TrackSequence == 0) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - "Can't found track containing requested sector"); - - if(sectorSuffix == null || sectorPrefix == null) return ReadSector(sectorAddress); - - byte[] sector = new byte[2352]; - byte[] data = ReadSector(sectorAddress); - - switch(trk.TrackType) - { - case TrackType.Audio: - case TrackType.Data: return data; - case TrackType.CdMode1: - Array.Copy(sectorPrefix, (int)sectorAddress * 16, sector, 0, 16); - Array.Copy(data, 0, sector, 16, 2048); - Array.Copy(sectorSuffix, (int)sectorAddress * 288, sector, 2064, 288); - return sector; - case TrackType.CdMode2Formless: - case TrackType.CdMode2Form1: - case TrackType.CdMode2Form2: - Array.Copy(sectorPrefix, (int)sectorAddress * 16, sector, 0, 16); - Array.Copy(data, 0, sector, 16, 2336); - return sector; - } - - break; - case XmlMediaType.BlockMedia: - switch(imageInfo.MediaType) - { - case MediaType.AppleFileWare: - case MediaType.AppleProfile: - case MediaType.AppleSonySS: - case MediaType.AppleSonyDS: - case MediaType.AppleWidget: - case MediaType.PriamDataTower: return ReadSectorsLong(sectorAddress, 1); - } - - break; - } - - throw new FeatureNotPresentImageException("Feature not present in image"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureNotPresentImageException("Feature not present in image"); - - Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); - if(trk.TrackSequence != track) - throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); - - return ReadSectorLong(trk.TrackStartSector + sectorAddress); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - byte[] sectors; - byte[] data; - - switch(imageInfo.XmlMediaType) - { - case XmlMediaType.OpticalDisc: - Track trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && - sectorAddress <= t.TrackEndSector); - if(trk.TrackSequence == 0) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - "Can't found track containing requested sector"); - - if(sectorAddress + length > trk.TrackEndSector + 1) - throw new ArgumentOutOfRangeException(nameof(length), - $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); - - switch(trk.TrackType) - { - // These types only contain user data - case TrackType.Audio: - case TrackType.Data: return ReadSectors(sectorAddress, length); - // Join prefix (sync, header) with user data with suffix (edc, ecc p, ecc q) - case TrackType.CdMode1: - if(sectorPrefix == null || sectorSuffix == null) return ReadSectors(sectorAddress, length); - - sectors = new byte[2352 * length]; - data = ReadSectors(sectorAddress, length); - for(uint i = 0; i < length; i++) - { - Array.Copy(sectorPrefix, (int)((sectorAddress + i) * 16), sectors, (int)(i * 2352), - 16); - Array.Copy(data, (int)(i * 2048), sectors, - (int)(i * 2352) + 16, 2048); - Array.Copy(sectorSuffix, (int)((sectorAddress + i) * 288), sectors, - (int)(i * 2352) + 2064, 288); - } - - return sectors; - // Join prefix (sync, header) with user data - case TrackType.CdMode2Formless: - case TrackType.CdMode2Form1: - case TrackType.CdMode2Form2: - if(sectorPrefix == null || sectorSuffix == null) return ReadSectors(sectorAddress, length); - - sectors = new byte[2352 * length]; - data = ReadSectors(sectorAddress, length); - for(uint i = 0; i < length; i++) - { - Array.Copy(sectorPrefix, (int)((sectorAddress + i) * 16), sectors, (int)(i * 2352), - 16); - Array.Copy(data, (int)(i * 2336), sectors, - (int)(i * 2352) + 16, 2336); - } - - return sectors; - } - - break; - case XmlMediaType.BlockMedia: - switch(imageInfo.MediaType) - { - // Join user data with tags - case MediaType.AppleFileWare: - case MediaType.AppleProfile: - case MediaType.AppleSonySS: - case MediaType.AppleSonyDS: - case MediaType.AppleWidget: - case MediaType.PriamDataTower: - if(sectorSubchannel == null) return ReadSector(sectorAddress); - - uint tagSize = 0; - switch(imageInfo.MediaType) - { - case MediaType.AppleFileWare: - case MediaType.AppleProfile: - case MediaType.AppleWidget: - tagSize = 20; - break; - case MediaType.AppleSonySS: - case MediaType.AppleSonyDS: - tagSize = 12; - break; - case MediaType.PriamDataTower: - tagSize = 24; - break; - } - - uint sectorSize = 512 + tagSize; - data = ReadSectors(sectorAddress, length); - sectors = new byte[(sectorSize + 512) * length]; - for(uint i = 0; i < length; i++) - { - Array.Copy(sectorSubchannel, (int)((sectorAddress + i) * tagSize), sectors, - (int)(i * sectorSize + 512), tagSize); - Array.Copy(data, (int)((sectorAddress + i) * 512), sectors, (int)(i * 512), 512); - } - - return sectors; - } - - break; - } - - throw new FeatureNotPresentImageException("Feature not present in image"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureNotPresentImageException("Feature not present in image"); - - Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); - if(trk.TrackSequence != track) - throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); - - if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) - throw new ArgumentOutOfRangeException(nameof(length), - $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); - - return ReadSectorsLong(trk.TrackStartSector + sectorAddress, length); - } - - public List<Track> GetSessionTracks(Session session) - { - return Tracks.Where(t => t.TrackSequence == session.SessionSequence).ToList(); - } - - public List<Track> GetSessionTracks(ushort session) - { - return Tracks.Where(t => t.TrackSequence == session).ToList(); - } - - public bool? VerifySector(ulong sectorAddress) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) return null; - - byte[] buffer = ReadSectorLong(sectorAddress); - return CdChecksums.CheckCdSector(buffer); - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) return null; - - byte[] buffer = ReadSectorLong(sectorAddress, track); - return CdChecksums.CheckCdSector(buffer); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - // Right now only CompactDisc sectors are verifyable - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - { - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - byte[] buffer = ReadSectorsLong(sectorAddress, length); - int bps = (int)(buffer.Length / length); - byte[] sector = new byte[bps]; - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(int i = 0; i < length; i++) - { - Array.Copy(buffer, i * bps, sector, 0, bps); - bool? sectorStatus = CdChecksums.CheckCdSector(sector); - - switch(sectorStatus) - { - case null: - unknownLbas.Add((ulong)i + sectorAddress); - break; - case false: - failingLbas.Add((ulong)i + sectorAddress); - break; - } - } - - if(unknownLbas.Count > 0) return null; - - return failingLbas.Count <= 0; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - // Right now only CompactDisc sectors are verifyable - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - byte[] buffer = ReadSectorsLong(sectorAddress, length, track); - int bps = (int)(buffer.Length / length); - byte[] sector = new byte[bps]; - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(int i = 0; i < length; i++) - { - Array.Copy(buffer, i * bps, sector, 0, bps); - bool? sectorStatus = CdChecksums.CheckCdSector(sector); - - switch(sectorStatus) - { - case null: - unknownLbas.Add((ulong)i + sectorAddress); - break; - case false: - failingLbas.Add((ulong)i + sectorAddress); - break; - } - } - - if(unknownLbas.Count > 0) return null; - - return failingLbas.Count <= 0; - } - - public bool? VerifyMediaImage() - { - // This will traverse all blocks and check their CRC64 without uncompressing them - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Checking index integrity at {0}", - header.indexOffset); - imageStream.Position = (long)header.indexOffset; - - IndexHeader idxHeader = new IndexHeader(); - structureBytes = new byte[Marshal.SizeOf(idxHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(idxHeader)); - idxHeader = (IndexHeader)Marshal.PtrToStructure(structurePointer, typeof(IndexHeader)); - Marshal.FreeHGlobal(structurePointer); - - if(idxHeader.identifier != BlockType.Index) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Incorrect index identifier"); - return false; - } - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", - header.indexOffset, idxHeader.entries); - - structureBytes = new byte[Marshal.SizeOf(typeof(IndexEntry)) * idxHeader.entries]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - Crc64Context.Data(structureBytes, out byte[] verifyCrc); - - if(BitConverter.ToUInt64(verifyCrc, 0) != idxHeader.crc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Expected index CRC {0:X16} but got {1:X16}", - idxHeader.crc64, BitConverter.ToUInt64(verifyCrc, 0)); - return false; - } - - imageStream.Position -= structureBytes.Length; - - List<IndexEntry> vrIndex = new List<IndexEntry>(); - for(ushort i = 0; i < idxHeader.entries; i++) - { - IndexEntry entry = new IndexEntry(); - structureBytes = new byte[Marshal.SizeOf(entry)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(entry)); - entry = (IndexEntry)Marshal.PtrToStructure(structurePointer, typeof(IndexEntry)); - Marshal.FreeHGlobal(structurePointer); - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Block type {0} with data type {1} is indexed to be at {2}", entry.blockType, - entry.dataType, entry.offset); - vrIndex.Add(entry); - } - - // Read up to 1MiB at a time for verification - const int VERIFY_SIZE = 1024 * 1024; - - foreach(IndexEntry entry in vrIndex) - { - imageStream.Position = (long)entry.offset; - Crc64Context crcVerify; - ulong readBytes; - byte[] verifyBytes; - - switch(entry.blockType) - { - case BlockType.DataBlock: - BlockHeader blockHeader = new BlockHeader(); - structureBytes = new byte[Marshal.SizeOf(blockHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); - blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); - Marshal.FreeHGlobal(structurePointer); - - crcVerify = new Crc64Context(); - readBytes = 0; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Verifying data block type {0} at position {1}", entry.dataType, - entry.offset); - - while(readBytes + VERIFY_SIZE < blockHeader.cmpLength) - { - verifyBytes = new byte[VERIFY_SIZE]; - imageStream.Read(verifyBytes, 0, verifyBytes.Length); - crcVerify.Update(verifyBytes); - readBytes += (ulong)verifyBytes.LongLength; - } - - verifyBytes = new byte[blockHeader.cmpLength - readBytes]; - imageStream.Read(verifyBytes, 0, verifyBytes.Length); - crcVerify.Update(verifyBytes); - - verifyCrc = crcVerify.Final(); - - if(BitConverter.ToUInt64(verifyCrc, 0) != blockHeader.cmpCrc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Expected block CRC {0:X16} but got {1:X16}", - blockHeader.cmpCrc64, BitConverter.ToUInt64(verifyCrc, 0)); - return false; - } - - break; - case BlockType.DeDuplicationTable: - DdtHeader ddtHeader = new DdtHeader(); - structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(ddtHeader)); - ddtHeader = (DdtHeader)Marshal.PtrToStructure(structurePointer, typeof(DdtHeader)); - Marshal.FreeHGlobal(structurePointer); - - crcVerify = new Crc64Context(); - readBytes = 0; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Verifying deduplication table type {0} at position {1}", - entry.dataType, entry.offset); - - while(readBytes + VERIFY_SIZE < ddtHeader.cmpLength) - { - verifyBytes = new byte[readBytes]; - imageStream.Read(verifyBytes, 0, verifyBytes.Length); - crcVerify.Update(verifyBytes); - readBytes += (ulong)verifyBytes.LongLength; - } - - verifyBytes = new byte[ddtHeader.cmpLength - readBytes]; - imageStream.Read(verifyBytes, 0, verifyBytes.Length); - crcVerify.Update(verifyBytes); - - verifyCrc = crcVerify.Final(); - - if(BitConverter.ToUInt64(verifyCrc, 0) != ddtHeader.cmpCrc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Expected DDT CRC {0:X16} but got {1:X16}", ddtHeader.cmpCrc64, - BitConverter.ToUInt64(verifyCrc, 0)); - return false; - } - - break; - case BlockType.TracksBlock: - TracksHeader trkHeader = new TracksHeader(); - structureBytes = new byte[Marshal.SizeOf(trkHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trkHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(trkHeader)); - trkHeader = (TracksHeader)Marshal.PtrToStructure(structurePointer, typeof(TracksHeader)); - Marshal.FreeHGlobal(structurePointer); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Track block at {0} contains {1} entries", header.indexOffset, - trkHeader.entries); - - structureBytes = new byte[Marshal.SizeOf(typeof(TrackEntry)) * trkHeader.entries]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - Crc64Context.Data(structureBytes, out verifyCrc); - - if(BitConverter.ToUInt64(verifyCrc, 0) != trkHeader.crc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Expected index CRC {0:X16} but got {1:X16}", trkHeader.crc64, - BitConverter.ToUInt64(verifyCrc, 0)); - return false; - } - - break; - default: - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Ignored field type {0}", - entry.blockType); - break; - } - } - - return true; - } - - public List<DumpHardwareType> DumpHardware { get; private set; } - public CICMMetadataType CicmMetadata { get; private set; } - - public IEnumerable<MediaTagType> SupportedMediaTags => - Enum.GetValues(typeof(MediaTagType)).Cast<MediaTagType>(); - public IEnumerable<SectorTagType> SupportedSectorTags => - Enum.GetValues(typeof(SectorTagType)).Cast<SectorTagType>(); - public IEnumerable<MediaType> SupportedMediaTypes => Enum.GetValues(typeof(MediaType)).Cast<MediaType>(); - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new[] - { - ("sectors_per_block", typeof(uint), - "How many sectors to store per block (will be rounded to next power of two)"), - ("dictionary", typeof(uint), "Size, in bytes, of the LZMA dictionary"), - ("max_ddt_size", typeof(uint), - "Maximum size, in mebibytes, for in-memory DDT. If image needs a bigger one, it will be on-disk"), - ("md5", typeof(bool), "Calculate and store MD5 of image's user data"), - ("sha1", typeof(bool), "Calculate and store SHA1 of image's user data"), - ("sha256", typeof(bool), "Calculate and store SHA256 of image's user data"), - ("spamsum", typeof(bool), "Calculate and store SpamSum of image's user data"), - ("deduplicate", typeof(bool), - "Store only unique sectors. This consumes more memory and is slower, but it's enabled by default"), - ("nocompress", typeof(bool), "Don't compress user data blocks. Other blocks will still be compressed") - }; - public IEnumerable<string> KnownExtensions => new[] {".dicf"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - uint sectorsPerBlock; - uint dictionary; - uint maxDdtSize; - bool doMd5; - bool doSha1; - bool doSha256; - bool doSpamsum; - - if(options != null) - { - if(options.TryGetValue("sectors_per_block", out string tmpValue)) - { - if(!uint.TryParse(tmpValue, out sectorsPerBlock)) - { - ErrorMessage = "Invalid value for sectors_per_block option"; - return false; - } - } - else sectorsPerBlock = 4096; - - if(options.TryGetValue("dictionary", out tmpValue)) - { - if(!uint.TryParse(tmpValue, out dictionary)) - { - ErrorMessage = "Invalid value for dictionary option"; - return false; - } - } - else dictionary = 1 << 25; - - if(options.TryGetValue("max_ddt_size", out tmpValue)) - { - if(!uint.TryParse(tmpValue, out maxDdtSize)) - { - ErrorMessage = "Invalid value for max_ddt_size option"; - return false; - } - } - else maxDdtSize = 256; - - if(options.TryGetValue("md5", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out doMd5)) - { - ErrorMessage = "Invalid value for md5 option"; - return false; - } - } - else doMd5 = false; - - if(options.TryGetValue("sha1", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out doSha1)) - { - ErrorMessage = "Invalid value for sha1 option"; - return false; - } - } - else doSha1 = false; - - if(options.TryGetValue("sha256", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out doSha256)) - { - ErrorMessage = "Invalid value for sha256 option"; - return false; - } - } - else doSha256 = false; - - if(options.TryGetValue("spamsum", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out doSpamsum)) - { - ErrorMessage = "Invalid value for spamsum option"; - return false; - } - } - else doSpamsum = false; - - if(options.TryGetValue("deduplicate", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out deduplicate)) - { - ErrorMessage = "Invalid value for deduplicate option"; - return false; - } - } - else deduplicate = true; - - if(options.TryGetValue("nocompress", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out nocompress)) - { - ErrorMessage = "Invalid value for nocompress option"; - return false; - } - } - else nocompress = false; - } - else - { - sectorsPerBlock = 4096; - dictionary = 1 << 25; - maxDdtSize = 256; - doMd5 = false; - doSha1 = false; - doSha256 = false; - doSpamsum = false; - deduplicate = true; - nocompress = false; - } - - // This really, cannot happen - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - // Calculate shift - shift = 0; - uint oldSectorsPerBlock = sectorsPerBlock; - while(sectorsPerBlock > 1) - { - sectorsPerBlock >>= 1; - shift++; - } - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Got a shift of {0} for {1} sectors per block", - shift, oldSectorsPerBlock); - - imageInfo = new ImageInfo - { - MediaType = mediaType, - SectorSize = sectorSize, - Sectors = sectors, - XmlMediaType = GetXmlMediaType(mediaType) - }; - - try { imageStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - // Check if appending to an existing image - if(imageStream.Length > Marshal.SizeOf(typeof(DicHeader))) - { - header = new DicHeader(); - structureBytes = new byte[Marshal.SizeOf(header)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); - header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); - Marshal.FreeHGlobal(structurePointer); - - if(header.identifier != DIC_MAGIC) - { - ErrorMessage = "Cannot append to a non DiscImageChef format image"; - return false; - } - - if(header.imageMajorVersion > DICF_VERSION) - { - ErrorMessage = $"Cannot append to an unknown image version {header.imageMajorVersion}"; - return false; - } - - if(header.mediaType != mediaType) - { - ErrorMessage = - $"Cannot write a media with type {mediaType} to an image with type {header.mediaType}"; - return false; - } - } - else - { - header = new DicHeader - { - identifier = DIC_MAGIC, - mediaType = mediaType, - creationTime = DateTime.UtcNow.ToFileTimeUtc() - }; - - imageStream.Write(new byte[Marshal.SizeOf(typeof(DicHeader))], 0, Marshal.SizeOf(typeof(DicHeader))); - } - - header.application = "DiscImageChef"; - header.imageMajorVersion = DICF_VERSION; - header.imageMinorVersion = 0; - header.applicationMajorVersion = (byte)typeof(DiscImageChef).Assembly.GetName().Version.Major; - header.applicationMinorVersion = (byte)typeof(DiscImageChef).Assembly.GetName().Version.Minor; - - index = new List<IndexEntry>(); - - // If there exists an index, we are appending, so read index - if(header.indexOffset > 0) - { - // Can't calculate checksum of an appended image - md5Provider = null; - sha1Provider = null; - sha256Provider = null; - spamsumProvider = null; - - imageStream.Position = (long)header.indexOffset; - IndexHeader idxHeader = new IndexHeader(); - structureBytes = new byte[Marshal.SizeOf(idxHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(idxHeader)); - idxHeader = (IndexHeader)Marshal.PtrToStructure(structurePointer, typeof(IndexHeader)); - Marshal.FreeHGlobal(structurePointer); - - if(idxHeader.identifier != BlockType.Index) - { - ErrorMessage = "Index not found in existing image, cannot continue"; - return false; - } - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", - header.indexOffset, idxHeader.entries); - - for(ushort i = 0; i < idxHeader.entries; i++) - { - IndexEntry entry = new IndexEntry(); - structureBytes = new byte[Marshal.SizeOf(entry)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(entry)); - entry = (IndexEntry)Marshal.PtrToStructure(structurePointer, typeof(IndexEntry)); - Marshal.FreeHGlobal(structurePointer); - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Block type {0} with data type {1} is indexed to be at {2}", - entry.blockType, entry.dataType, entry.offset); - index.Add(entry); - } - - // Invalidate previous checksum block - index.RemoveAll(t => t.blockType == BlockType.ChecksumBlock && t.dataType == DataType.NoData); - - bool foundUserDataDdt = false; - foreach(IndexEntry entry in index) - { - imageStream.Position = (long)entry.offset; - switch(entry.blockType) - { - case BlockType.DataBlock: - switch(entry.dataType) - { - case DataType.CdSectorPrefix: - case DataType.CdSectorSuffix: - case DataType.CdSectorSubchannel: - case DataType.AppleProfileTag: - case DataType.AppleSonyTag: - case DataType.PriamDataTowerTag: break; - default: continue; - } - - BlockHeader blockHeader = new BlockHeader(); - structureBytes = new byte[Marshal.SizeOf(blockHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); - blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); - Marshal.FreeHGlobal(structurePointer); - imageInfo.ImageSize += blockHeader.cmpLength; - - if(blockHeader.identifier != entry.blockType) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect identifier for data block at position {0}", - entry.offset); - break; - } - - if(blockHeader.type != entry.dataType) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Expected block with data type {0} at position {1} but found data type {2}", - entry.dataType, entry.offset, blockHeader.type); - break; - } - - byte[] data; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found data block type {0} at position {1}", entry.dataType, - entry.offset); - - if(blockHeader.compression == CompressionType.Lzma) - { - byte[] compressedTag = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; - byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; - imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); - imageStream.Read(compressedTag, 0, compressedTag.Length); - MemoryStream compressedTagMs = new MemoryStream(compressedTag); - LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedTagMs); - data = new byte[blockHeader.length]; - lzmaBlock.Read(data, 0, (int)blockHeader.length); - lzmaBlock.Close(); - compressedTagMs.Close(); - } - else if(blockHeader.compression == CompressionType.None) - { - data = new byte[blockHeader.length]; - imageStream.Read(data, 0, (int)blockHeader.length); - } - else - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found unknown compression type {0}, continuing...", - (ushort)blockHeader.compression); - break; - } - - Crc64Context.Data(data, out byte[] blockCrc); - blockCrc = blockCrc.ToArray(); - if(BitConverter.ToUInt64(blockCrc, 0) != blockHeader.crc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", - BitConverter.ToUInt64(blockCrc, 0), blockHeader.crc64); - break; - } - - switch(entry.dataType) - { - case DataType.CdSectorPrefix: - sectorPrefix = data; - break; - case DataType.CdSectorSuffix: - sectorSuffix = data; - break; - case DataType.CdSectorSubchannel: - case DataType.AppleProfileTag: - case DataType.AppleSonyTag: - case DataType.PriamDataTowerTag: - sectorSubchannel = data; - break; - } - - break; - case BlockType.DeDuplicationTable: - // Only user data deduplication tables are used right now - if(entry.dataType != DataType.UserData) break; - - DdtHeader ddtHeader = new DdtHeader(); - structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(ddtHeader)); - ddtHeader = (DdtHeader)Marshal.PtrToStructure(structurePointer, typeof(DdtHeader)); - Marshal.FreeHGlobal(structurePointer); - - if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; - - if(ddtHeader.entries != imageInfo.Sectors) - { - ErrorMessage = - $"Trying to write a media with {imageInfo.Sectors} sectors to an image with {ddtHeader.entries} sectors, not continuing..."; - return false; - } - - shift = ddtHeader.shift; - - switch(ddtHeader.compression) - { - case CompressionType.Lzma: - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Decompressing DDT..."); - DateTime ddtStart = DateTime.UtcNow; - byte[] compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; - byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; - imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); - imageStream.Read(compressedDdt, 0, compressedDdt.Length); - MemoryStream compressedDdtMs = new MemoryStream(compressedDdt); - LzmaStream lzmaDdt = new LzmaStream(lzmaProperties, compressedDdtMs); - byte[] decompressedDdt = new byte[ddtHeader.length]; - lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); - lzmaDdt.Close(); - compressedDdtMs.Close(); - userDataDdt = new ulong[ddtHeader.entries]; - for(ulong i = 0; i < ddtHeader.entries; i++) - userDataDdt[i] = - BitConverter.ToUInt64(decompressedDdt, (int)(i * sizeof(ulong))); - DateTime ddtEnd = DateTime.UtcNow; - inMemoryDdt = true; - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Took {0} seconds to decompress DDT", - (ddtEnd - ddtStart).TotalSeconds); - break; - case CompressionType.None: - inMemoryDdt = false; - outMemoryDdtPosition = (long)entry.offset; - break; - default: - throw new - ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)ddtHeader.compression}"); - } - - foundUserDataDdt = true; - break; - // CICM XML metadata block - case BlockType.CicmBlock: - CicmMetadataBlock cicmBlock = new CicmMetadataBlock(); - structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(cicmBlock)); - cicmBlock = (CicmMetadataBlock)Marshal.PtrToStructure(structurePointer, - typeof(CicmMetadataBlock)); - Marshal.FreeHGlobal(structurePointer); - if(cicmBlock.identifier != BlockType.CicmBlock) break; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found CICM XML metadata block at position {0}", entry.offset); - - byte[] cicmBytes = new byte[cicmBlock.length]; - imageStream.Read(cicmBytes, 0, cicmBytes.Length); - MemoryStream cicmMs = new MemoryStream(cicmBytes); - XmlSerializer cicmXs = new XmlSerializer(typeof(CICMMetadataType)); - try - { - StreamReader sr = new StreamReader(cicmMs); - CicmMetadata = (CICMMetadataType)cicmXs.Deserialize(sr); - sr.Close(); - } - catch(XmlException ex) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Exception {0} processing CICM XML metadata block", - ex.Message); - CicmMetadata = null; - } - - break; - // Dump hardware block - case BlockType.DumpHardwareBlock: - DumpHardwareHeader dumpBlock = new DumpHardwareHeader(); - structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpBlock)); - dumpBlock = (DumpHardwareHeader)Marshal.PtrToStructure(structurePointer, - typeof(DumpHardwareHeader)); - Marshal.FreeHGlobal(structurePointer); - if(dumpBlock.identifier != BlockType.DumpHardwareBlock) break; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Found dump hardware block at position {0}", entry.offset); - - structureBytes = new byte[dumpBlock.length]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - Crc64Context.Data(structureBytes, out byte[] dumpCrc); - if(BitConverter.ToUInt64(dumpCrc, 0) != dumpBlock.crc64) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", - BitConverter.ToUInt64(dumpCrc, 0), dumpBlock.crc64); - break; - } - - imageStream.Position -= structureBytes.Length; - - DumpHardware = new List<DumpHardwareType>(); - - for(ushort i = 0; i < dumpBlock.entries; i++) - { - DumpHardwareEntry dumpEntry = new DumpHardwareEntry(); - structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; - imageStream.Read(structureBytes, 0, structureBytes.Length); - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); - Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpEntry)); - dumpEntry = (DumpHardwareEntry)Marshal.PtrToStructure(structurePointer, - typeof(DumpHardwareEntry)); - Marshal.FreeHGlobal(structurePointer); - - DumpHardwareType dump = new DumpHardwareType - { - Software = new SoftwareType(), - Extents = new ExtentType[dumpEntry.extents] - }; - - byte[] tmp; - - if(dumpEntry.manufacturerLength > 0) - { - tmp = new byte[dumpEntry.manufacturerLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Manufacturer = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.modelLength > 0) - { - tmp = new byte[dumpEntry.modelLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Model = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.revisionLength > 0) - { - tmp = new byte[dumpEntry.revisionLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Revision = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.firmwareLength > 0) - { - tmp = new byte[dumpEntry.firmwareLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Firmware = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.serialLength > 0) - { - tmp = new byte[dumpEntry.serialLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Serial = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.softwareNameLength > 0) - { - tmp = new byte[dumpEntry.softwareNameLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Software.Name = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.softwareVersionLength > 0) - { - tmp = new byte[dumpEntry.softwareVersionLength - 1]; - imageStream.Read(tmp, 0, tmp.Length); - imageStream.Position += 1; - dump.Software.Version = Encoding.UTF8.GetString(tmp); - } - - if(dumpEntry.softwareOperatingSystemLength > 0) - { - tmp = new byte[dumpEntry.softwareOperatingSystemLength - 1]; - imageStream.Position += 1; - imageStream.Read(tmp, 0, tmp.Length); - dump.Software.OperatingSystem = Encoding.UTF8.GetString(tmp); - } - - tmp = new byte[16]; - for(uint j = 0; j < dumpEntry.extents; j++) - { - imageStream.Read(tmp, 0, tmp.Length); - dump.Extents[j] = new ExtentType - { - Start = BitConverter.ToUInt64(tmp, 0), - End = BitConverter.ToUInt64(tmp, 8) - }; - } - - dump.Extents = dump.Extents.OrderBy(t => t.Start).ToArray(); - if(dump.Extents.Length > 0) DumpHardware.Add(dump); - } - - if(DumpHardware.Count == 0) DumpHardware = null; - break; - } - } - - if(!foundUserDataDdt) - { - ErrorMessage = "Could not find user data deduplication table."; - return false; - } - } - // Creating new - else - { - // Checking that DDT is smaller than requested size - inMemoryDdt = sectors <= maxDdtSize * 1024 * 1024 / sizeof(ulong); - - // If in memory, easy - if(inMemoryDdt) userDataDdt = new ulong[sectors]; - // If not, create the block, add to index, and enlarge the file to allow the DDT to exist on-disk - else - { - outMemoryDdtPosition = imageStream.Position; - index.Add(new IndexEntry - { - blockType = BlockType.DeDuplicationTable, - dataType = DataType.UserData, - offset = (ulong)outMemoryDdtPosition - }); - - // CRC64 will be calculated later - DdtHeader ddtHeader = new DdtHeader - { - identifier = BlockType.DeDuplicationTable, - type = DataType.UserData, - compression = CompressionType.None, - shift = shift, - entries = sectors, - cmpLength = sectors * sizeof(ulong), - length = sectors * sizeof(ulong) - }; - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); - structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; - Marshal.StructureToPtr(ddtHeader, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - structureBytes = null; - - imageStream.Position += (long)(sectors * sizeof(ulong)) - 1; - imageStream.WriteByte(0); - } - - if(doMd5) md5Provider = new Md5Context(); - - if(doSha1) sha1Provider = new Sha1Context(); - - if(doSha256) sha256Provider = new Sha256Context(); - - if(doSpamsum) spamsumProvider = new SpamSumContext(); - } - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "In memory DDT?: {0}", inMemoryDdt); - - // Initialize tables - imageStream.Seek(0, SeekOrigin.End); - mediaTags = new Dictionary<MediaTagType, byte[]>(); - checksumProvider = SHA256.Create(); - deduplicationTable = new Dictionary<string, ulong>(); - trackIsrcs = new Dictionary<byte, string>(); - trackFlags = new Dictionary<byte, byte>(); - - // Initialize compressors properties (all maxed) - lzmaEncoderProperties = new LzmaEncoderProperties(true, (int)dictionary, 273); - flakeWriterSettings = new FlakeWriterSettings - { - PCM = AudioPCMConfig.RedBook, - DoMD5 = false, - BlockSize = (1 << shift) * SAMPLES_PER_SECTOR, - MinFixedOrder = 0, - MaxFixedOrder = 4, - MinLPCOrder = 1, - MaxLPCOrder = 32, - MaxPartitionOrder = 8, - StereoMethod = StereoMethod.Evaluate, - PredictionType = PredictionType.Search, - WindowMethod = WindowMethod.EvaluateN, - EstimationDepth = 5, - MinPrecisionSearch = 1, - MaxPrecisionSearch = 1, - TukeyParts = 0, - TukeyOverlap = 1.0, - TukeyP = 1.0, - AllowNonSubset = true - }; - - // Check if FLAKE's block size is bigger than what we want - if(flakeWriterSettings.BlockSize > MAX_FLAKE_BLOCK) flakeWriterSettings.BlockSize = MAX_FLAKE_BLOCK; - if(flakeWriterSettings.BlockSize < MIN_FLAKE_BLOCK) flakeWriterSettings.BlockSize = MIN_FLAKE_BLOCK; - FlakeWriter.Vendor = "DiscImageChef"; - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(mediaTags.ContainsKey(tag)) mediaTags.Remove(tag); - - mediaTags.Add(tag, data); - - ErrorMessage = ""; - return true; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(sectorAddress >= Info.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - if((imageInfo.XmlMediaType != XmlMediaType.OpticalDisc || !writingLong) && !rewinded) - { - if(sectorAddress <= lastWrittenBlock && alreadyWrittenZero) - { - rewinded = true; - md5Provider = null; - sha1Provider = null; - sha256Provider = null; - spamsumProvider = null; - } - - md5Provider?.Update(data); - sha1Provider?.Update(data); - sha256Provider?.Update(data); - spamsumProvider?.Update(data); - lastWrittenBlock = sectorAddress; - } - - if(sectorAddress == 0) alreadyWrittenZero = true; - - byte[] hash = null; - writtenSectors++; - - // Compute hash only if asked to deduplicate, or the sector is empty (those will always be deduplicated) - if(deduplicate || ArrayHelpers.ArrayIsNullOrEmpty(data)) hash = checksumProvider.ComputeHash(data); - string hashString = null; - - if(hash != null) - { - StringBuilder hashSb = new StringBuilder(); - foreach(byte h in hash) hashSb.Append(h.ToString("x2")); - hashString = hashSb.ToString(); - - if(deduplicationTable.TryGetValue(hashString, out ulong pointer)) - { - SetDdtEntry(sectorAddress, pointer); - ErrorMessage = ""; - return true; - } - } - - Track trk = new Track(); - - // If optical disc check track - if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) - { - trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && - sectorAddress <= t.TrackEndSector); - if(trk.TrackSequence == 0) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - "Can't found track containing requested sector"); - } - - // Close current block first - if(blockStream != null && - // When sector siz changes - (currentBlockHeader.sectorSize != data.Length || - // When block if filled - currentBlockOffset == 1 << shift || - // When we change to/from CompactDisc audio - currentBlockHeader.compression == CompressionType.Flac && trk.TrackType != TrackType.Audio)) - { - currentBlockHeader.length = currentBlockOffset * currentBlockHeader.sectorSize; - currentBlockHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0); - - Crc64Context cmpCrc64Context = new Crc64Context(); - - byte[] lzmaProperties = new byte[0]; - - if(currentBlockHeader.compression == CompressionType.Flac) - { - long remaining = currentBlockOffset * SAMPLES_PER_SECTOR % flakeWriter.Settings.BlockSize; - // Fill FLAC block - if(remaining != 0) - { - AudioBuffer audioBuffer = - new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); - flakeWriter.Write(audioBuffer); - } - - // This trick because CUETools.Codecs.Flake closes the underlying stream - long realLength = blockStream.Length; - byte[] buffer = new byte[realLength]; - flakeWriter.Close(); - Array.Copy(blockStream.GetBuffer(), 0, buffer, 0, realLength); - blockStream = new MemoryStream(buffer); - } - else if(currentBlockHeader.compression == CompressionType.Lzma) - { - lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - cmpCrc64Context.Update(lzmaProperties); - if(blockStream.Length > decompressedStream.Length) - currentBlockHeader.compression = CompressionType.None; - } - - if(currentBlockHeader.compression == CompressionType.None) - { - blockStream = decompressedStream; - currentBlockHeader.cmpCrc64 = currentBlockHeader.crc64; - } - else - { - cmpCrc64Context.Update(blockStream.ToArray()); - currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); - } - - currentBlockHeader.cmpLength = (uint)blockStream.Length; - if(currentBlockHeader.compression == CompressionType.Lzma) - currentBlockHeader.cmpLength += LZMA_PROPERTIES_LENGTH; - - index.Add(new IndexEntry - { - blockType = BlockType.DataBlock, - dataType = DataType.UserData, - offset = (ulong)imageStream.Position - }); - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(currentBlockHeader)); - structureBytes = new byte[Marshal.SizeOf(currentBlockHeader)]; - Marshal.StructureToPtr(currentBlockHeader, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - structureBytes = null; - if(currentBlockHeader.compression == CompressionType.Lzma) - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - blockStream = null; - currentBlockOffset = 0; - } - - // No block set - if(blockStream == null) - { - currentBlockHeader = new BlockHeader - { - identifier = BlockType.DataBlock, - type = DataType.UserData, - compression = nocompress ? CompressionType.None : CompressionType.Lzma, - sectorSize = (uint)data.Length - }; - - if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc && trk.TrackType == TrackType.Audio && !nocompress - ) currentBlockHeader.compression = CompressionType.Flac; - - // JaguarCD stores data in audio tracks. FLAC is too inefficient, use LZMA there. - if(imageInfo.MediaType == MediaType.JaguarCD && trk.TrackType == TrackType.Audio && - !nocompress && - currentBlockHeader.compression == CompressionType.Flac && - trk.TrackSession > 1) currentBlockHeader.compression = CompressionType.Lzma; - - blockStream = new MemoryStream(); - decompressedStream = new MemoryStream(); - if(currentBlockHeader.compression == CompressionType.Flac) - flakeWriter = new FlakeWriter("", blockStream, flakeWriterSettings) {DoSeekTable = false}; - else lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); - crc64 = new Crc64Context(); - } - - ulong ddtEntry = (ulong)((imageStream.Position << shift) + currentBlockOffset); - if(hash != null) deduplicationTable.Add(hashString, ddtEntry); - if(currentBlockHeader.compression == CompressionType.Flac) - { - AudioBuffer audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, data, SAMPLES_PER_SECTOR); - flakeWriter.Write(audioBuffer); - } - else - { - decompressedStream.Write(data, 0, data.Length); - if(currentBlockHeader.compression == CompressionType.Lzma) lzmaBlockStream.Write(data, 0, data.Length); - } - - SetDdtEntry(sectorAddress, ddtEntry); - crc64.Update(data); - currentBlockOffset++; - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(sectorAddress + length > Info.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - uint sectorSize = (uint)(data.Length / length); - - for(uint i = 0; i < length; i++) - { - byte[] tmp = new byte[sectorSize]; - Array.Copy(data, i * sectorSize, tmp, 0, sectorSize); - if(!WriteSector(tmp, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - byte[] sector; - - switch(imageInfo.XmlMediaType) - { - case XmlMediaType.OpticalDisc: - Track track = - Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(data.Length != 2352) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - writingLong = true; - if(!rewinded) - { - if(sectorAddress <= lastWrittenBlock && alreadyWrittenZero) - { - rewinded = true; - md5Provider = null; - sha1Provider = null; - sha256Provider = null; - spamsumProvider = null; - } - - md5Provider?.Update(data); - sha1Provider?.Update(data); - sha256Provider?.Update(data); - spamsumProvider?.Update(data); - lastWrittenBlock = sectorAddress; - } - - // Split raw cd sector data in prefix (sync, header), user data and suffix (edc, ecc p, ecc q) - switch(track.TrackType) - { - case TrackType.Audio: - case TrackType.Data: return WriteSector(data, sectorAddress); - case TrackType.CdMode1: - if(sectorPrefix == null) sectorPrefix = new byte[imageInfo.Sectors * 16]; - if(sectorSuffix == null) sectorSuffix = new byte[imageInfo.Sectors * 288]; - sector = new byte[2048]; - Array.Copy(data, 0, sectorPrefix, (int)sectorAddress * 16, 16); - Array.Copy(data, 16, sector, 0, 2048); - Array.Copy(data, 2064, sectorSuffix, (int)sectorAddress * 288, 288); - return WriteSector(sector, sectorAddress); - case TrackType.CdMode2Formless: - case TrackType.CdMode2Form1: - case TrackType.CdMode2Form2: - if(sectorPrefix == null) sectorPrefix = new byte[imageInfo.Sectors * 16]; - if(sectorSuffix == null) sectorSuffix = new byte[imageInfo.Sectors * 288]; - sector = new byte[2336]; - Array.Copy(data, 0, sectorPrefix, (int)sectorAddress * 16, 16); - Array.Copy(data, 16, sector, 0, 2336); - return WriteSector(sector, sectorAddress); - } - - break; - case XmlMediaType.BlockMedia: - switch(imageInfo.MediaType) - { - // Split user data from Apple tags - case MediaType.AppleFileWare: - case MediaType.AppleProfile: - case MediaType.AppleSonyDS: - case MediaType.AppleSonySS: - case MediaType.AppleWidget: - case MediaType.PriamDataTower: - byte[] oldTag; - byte[] newTag; - - switch(data.Length - 512) - { - // Sony tag, convert to Profile - case 12 when imageInfo.MediaType == MediaType.AppleProfile || - imageInfo.MediaType == MediaType.AppleFileWare: - oldTag = new byte[12]; - Array.Copy(data, 512, oldTag, 0, 12); - newTag = LisaTag.DecodeSonyTag(oldTag)?.ToProfile().GetBytes(); - break; - // Sony tag, convert to Priam - case 12 when imageInfo.MediaType == MediaType.PriamDataTower: - oldTag = new byte[12]; - Array.Copy(data, 512, oldTag, 0, 12); - newTag = LisaTag.DecodeSonyTag(oldTag)?.ToPriam().GetBytes(); - break; - // Sony tag, copy to Sony - case 12 when imageInfo.MediaType == MediaType.AppleSonySS || - imageInfo.MediaType == MediaType.AppleSonySS: - newTag = new byte[12]; - Array.Copy(data, 512, newTag, 0, 12); - break; - // Profile tag, copy to Profile - case 20 when imageInfo.MediaType == MediaType.AppleProfile || - imageInfo.MediaType == MediaType.AppleFileWare: - newTag = new byte[20]; - Array.Copy(data, 512, newTag, 0, 20); - break; - // Profile tag, convert to Priam - case 20 when imageInfo.MediaType == MediaType.PriamDataTower: - oldTag = new byte[20]; - Array.Copy(data, 512, oldTag, 0, 20); - newTag = LisaTag.DecodeProfileTag(oldTag)?.ToPriam().GetBytes(); - break; - // Profile tag, convert to Sony - case 20 when imageInfo.MediaType == MediaType.AppleSonySS || - imageInfo.MediaType == MediaType.AppleSonySS: - oldTag = new byte[20]; - Array.Copy(data, 512, oldTag, 0, 20); - newTag = LisaTag.DecodeProfileTag(oldTag)?.ToSony().GetBytes(); - break; - // Priam tag, convert to Profile - case 24 when imageInfo.MediaType == MediaType.AppleProfile || - imageInfo.MediaType == MediaType.AppleFileWare: - oldTag = new byte[24]; - Array.Copy(data, 512, oldTag, 0, 24); - newTag = LisaTag.DecodePriamTag(oldTag)?.ToProfile().GetBytes(); - break; - // Priam tag, copy to Priam - case 12 when imageInfo.MediaType == MediaType.PriamDataTower: - newTag = new byte[24]; - Array.Copy(data, 512, newTag, 0, 24); - break; - // Priam tag, convert to Sony - case 24 when imageInfo.MediaType == MediaType.AppleSonySS || - imageInfo.MediaType == MediaType.AppleSonySS: - oldTag = new byte[24]; - Array.Copy(data, 512, oldTag, 0, 24); - newTag = LisaTag.DecodePriamTag(oldTag)?.ToSony().GetBytes(); - break; - case 0: - newTag = null; - break; - default: - ErrorMessage = "Incorrect data size"; - return false; - } - - sector = new byte[512]; - Array.Copy(data, 0, sector, 0, 512); - - if(newTag == null) return WriteSector(sector, sectorAddress); - - if(sectorSubchannel == null) - sectorSubchannel = new byte[newTag.Length * (int)imageInfo.Sectors]; - Array.Copy(newTag, 0, sectorSubchannel, newTag.Length * (int)sectorAddress, newTag.Length); - - return WriteSector(sector, sectorAddress); - } - - break; - } - - ErrorMessage = "Unknown long sector type, cannot write."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - byte[] sector; - switch(imageInfo.XmlMediaType) - { - case XmlMediaType.OpticalDisc: - Track track = - Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - if(data.Length % 2352 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > track.TrackEndSector + 1) - { - ErrorMessage = "Can't cross tracks"; - return false; - } - - sector = new byte[2352]; - for(uint i = 0; i < length; i++) - { - Array.Copy(data, 2352 * i, sector, 0, 2352); - if(!WriteSectorLong(sector, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - case XmlMediaType.BlockMedia: - switch(imageInfo.MediaType) - { - case MediaType.AppleFileWare: - case MediaType.AppleProfile: - case MediaType.AppleSonyDS: - case MediaType.AppleSonySS: - case MediaType.AppleWidget: - case MediaType.PriamDataTower: - int sectorSize = 0; - if(data.Length % 524 == 0) sectorSize = 524; - else if(data.Length % 532 == 0) sectorSize = 532; - else if(data.Length % 536 == 0) sectorSize = 536; - - if(sectorSize == 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - sector = new byte[sectorSize]; - for(uint i = 0; i < length; i++) - { - Array.Copy(data, sectorSize * i, sector, 0, sectorSize); - if(!WriteSectorLong(sector, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - } - - break; - } - - ErrorMessage = "Unknown long sector type, cannot write."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - Tracks = tracks; - ErrorMessage = ""; - return true; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - // Close current block first - if(blockStream != null) - { - currentBlockHeader.length = currentBlockOffset * currentBlockHeader.sectorSize; - currentBlockHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0); - - Crc64Context cmpCrc64Context = new Crc64Context(); - - byte[] lzmaProperties = new byte[0]; - - if(currentBlockHeader.compression == CompressionType.Flac) - { - long remaining = currentBlockOffset * SAMPLES_PER_SECTOR % flakeWriter.Settings.BlockSize; - // Fill FLAC block - if(remaining != 0) - { - AudioBuffer audioBuffer = - new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); - flakeWriter.Write(audioBuffer); - } - - // This trick because CUETools.Codecs.Flake closes the underlying stream - long realLength = blockStream.Length; - byte[] buffer = new byte[realLength]; - flakeWriter.Close(); - Array.Copy(blockStream.GetBuffer(), 0, buffer, 0, realLength); - blockStream = new MemoryStream(buffer); - } - else if(currentBlockHeader.compression == CompressionType.Lzma) - { - lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - cmpCrc64Context.Update(lzmaProperties); - if(blockStream.Length > decompressedStream.Length) - currentBlockHeader.compression = CompressionType.None; - } - - if(currentBlockHeader.compression == CompressionType.None) - { - blockStream = decompressedStream; - currentBlockHeader.cmpCrc64 = currentBlockHeader.crc64; - } - else - { - cmpCrc64Context.Update(blockStream.ToArray()); - currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); - } - - currentBlockHeader.cmpLength = (uint)blockStream.Length; - if(currentBlockHeader.compression == CompressionType.Lzma) - currentBlockHeader.cmpLength += LZMA_PROPERTIES_LENGTH; - - index.Add(new IndexEntry - { - blockType = BlockType.DataBlock, - dataType = DataType.UserData, - offset = (ulong)imageStream.Position - }); - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(currentBlockHeader)); - structureBytes = new byte[Marshal.SizeOf(currentBlockHeader)]; - Marshal.StructureToPtr(currentBlockHeader, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - structureBytes = null; - if(currentBlockHeader.compression == CompressionType.Lzma) - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - } - - if(deduplicate) - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Of {0} sectors written, {1} are unique ({2:P})", writtenSectors, - deduplicationTable.Count, (double)deduplicationTable.Count / writtenSectors); - - IndexEntry idxEntry; - - // Write media tag blocks - foreach(KeyValuePair<MediaTagType, byte[]> mediaTag in mediaTags) - { - DataType dataType = GetDataTypeForMediaTag(mediaTag.Key); - idxEntry = new IndexEntry - { - blockType = BlockType.DataBlock, - dataType = dataType, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tag type {0} to position {1}", - mediaTag.Key, idxEntry.offset); - - Crc64Context.Data(mediaTag.Value, out byte[] tagCrc); - - BlockHeader tagBlock = new BlockHeader - { - identifier = BlockType.DataBlock, - type = dataType, - length = (uint)mediaTag.Value.Length, - crc64 = BitConverter.ToUInt64(tagCrc, 0) - }; - - blockStream = new MemoryStream(); - lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); - lzmaBlockStream.Write(mediaTag.Value, 0, mediaTag.Value.Length); - byte[] lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - byte[] tagData; - - // Not compressible - if(blockStream.Length + LZMA_PROPERTIES_LENGTH >= mediaTag.Value.Length) - { - tagBlock.cmpLength = tagBlock.length; - tagBlock.cmpCrc64 = tagBlock.crc64; - tagData = mediaTag.Value; - tagBlock.compression = CompressionType.None; - } - else - { - tagData = blockStream.ToArray(); - Crc64Context crc64Ctx = new Crc64Context(); - crc64Ctx.Update(lzmaProperties); - crc64Ctx.Update(tagData); - tagCrc = crc64Ctx.Final(); - tagBlock.cmpLength = (uint)tagData.Length + LZMA_PROPERTIES_LENGTH; - tagBlock.cmpCrc64 = BitConverter.ToUInt64(tagCrc, 0); - tagBlock.compression = CompressionType.Lzma; - } - - lzmaBlockStream = null; - blockStream = null; - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(tagBlock)); - structureBytes = new byte[Marshal.SizeOf(tagBlock)]; - Marshal.StructureToPtr(tagBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - if(tagBlock.compression == CompressionType.Lzma) - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - imageStream.Write(tagData, 0, tagData.Length); - - index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == dataType); - - index.Add(idxEntry); - } - - // If we have set the geometry block, write it - if(geometryBlock.identifier == BlockType.GeometryBlock) - { - idxEntry = new IndexEntry - { - blockType = BlockType.GeometryBlock, - dataType = DataType.NoData, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing geometry block to position {0}", - idxEntry.offset); - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(geometryBlock)); - structureBytes = new byte[Marshal.SizeOf(geometryBlock)]; - Marshal.StructureToPtr(geometryBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - - index.RemoveAll(t => t.blockType == BlockType.GeometryBlock && t.dataType == DataType.NoData); - - index.Add(idxEntry); - } - - // If we have dump hardware, write it - if(DumpHardware != null) - { - MemoryStream dumpMs = new MemoryStream(); - foreach(DumpHardwareType dump in DumpHardware) - { - byte[] dumpManufacturer = null; - byte[] dumpModel = null; - byte[] dumpRevision = null; - byte[] dumpFirmware = null; - byte[] dumpSerial = null; - byte[] dumpSoftwareName = null; - byte[] dumpSoftwareVersion = null; - byte[] dumpSoftwareOperatingSystem = null; - - if(!string.IsNullOrWhiteSpace(dump.Manufacturer)) - dumpManufacturer = Encoding.UTF8.GetBytes(dump.Manufacturer); - if(!string.IsNullOrWhiteSpace(dump.Model)) dumpModel = Encoding.UTF8.GetBytes(dump.Model); - if(!string.IsNullOrWhiteSpace(dump.Revision)) dumpRevision = Encoding.UTF8.GetBytes(dump.Revision); - if(!string.IsNullOrWhiteSpace(dump.Firmware)) dumpFirmware = Encoding.UTF8.GetBytes(dump.Firmware); - if(!string.IsNullOrWhiteSpace(dump.Serial)) dumpSerial = Encoding.UTF8.GetBytes(dump.Serial); - if(dump.Software != null) - { - if(!string.IsNullOrWhiteSpace(dump.Software.Name)) - dumpSoftwareName = Encoding.UTF8.GetBytes(dump.Software.Name); - if(!string.IsNullOrWhiteSpace(dump.Software.Version)) - dumpSoftwareVersion = Encoding.UTF8.GetBytes(dump.Software.Version); - if(!string.IsNullOrWhiteSpace(dump.Software.OperatingSystem)) - dumpSoftwareOperatingSystem = Encoding.UTF8.GetBytes(dump.Software.OperatingSystem); - } - - DumpHardwareEntry dumpEntry = new DumpHardwareEntry - { - manufacturerLength = (uint)(dumpManufacturer?.Length + 1 ?? 0), - modelLength = (uint)(dumpModel?.Length + 1 ?? 0), - revisionLength = (uint)(dumpRevision?.Length + 1 ?? 0), - firmwareLength = (uint)(dumpFirmware?.Length + 1 ?? 0), - serialLength = (uint)(dumpSerial?.Length + 1 ?? 0), - softwareNameLength = (uint)(dumpSoftwareName?.Length + 1 ?? 0), - softwareVersionLength = (uint)(dumpSoftwareVersion?.Length + 1 ?? 0), - softwareOperatingSystemLength = (uint)(dumpSoftwareOperatingSystem?.Length + 1 ?? 0), - extents = (uint)dump.Extents.Length - }; - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); - structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; - Marshal.StructureToPtr(dumpEntry, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - dumpMs.Write(structureBytes, 0, structureBytes.Length); - - if(dumpManufacturer != null) - { - dumpMs.Write(dumpManufacturer, 0, dumpManufacturer.Length); - dumpMs.WriteByte(0); - } - - if(dumpModel != null) - { - dumpMs.Write(dumpModel, 0, dumpModel.Length); - dumpMs.WriteByte(0); - } - - if(dumpRevision != null) - { - dumpMs.Write(dumpRevision, 0, dumpRevision.Length); - dumpMs.WriteByte(0); - } - - if(dumpFirmware != null) - { - dumpMs.Write(dumpFirmware, 0, dumpFirmware.Length); - dumpMs.WriteByte(0); - } - - if(dumpSerial != null) - { - dumpMs.Write(dumpSerial, 0, dumpSerial.Length); - dumpMs.WriteByte(0); - } - - if(dumpSoftwareName != null) - { - dumpMs.Write(dumpSoftwareName, 0, dumpSoftwareName.Length); - dumpMs.WriteByte(0); - } - - if(dumpSoftwareVersion != null) - { - dumpMs.Write(dumpSoftwareVersion, 0, dumpSoftwareVersion.Length); - dumpMs.WriteByte(0); - } - - if(dumpSoftwareOperatingSystem != null) - { - dumpMs.Write(dumpSoftwareOperatingSystem, 0, dumpSoftwareOperatingSystem.Length); - dumpMs.WriteByte(0); - } - - foreach(ExtentType extent in dump.Extents) - { - dumpMs.Write(BitConverter.GetBytes(extent.Start), 0, sizeof(ulong)); - dumpMs.Write(BitConverter.GetBytes(extent.End), 0, sizeof(ulong)); - } - } - - idxEntry = new IndexEntry - { - blockType = BlockType.DumpHardwareBlock, - dataType = DataType.NoData, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing dump hardware block to position {0}", - idxEntry.offset); - - Crc64Context.Data(dumpMs.ToArray(), out byte[] dumpCrc); - DumpHardwareHeader dumpBlock = new DumpHardwareHeader - { - identifier = BlockType.DumpHardwareBlock, - entries = (ushort)DumpHardware.Count, - crc64 = BitConverter.ToUInt64(dumpCrc, 0), - length = (uint)dumpMs.Length - }; - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); - structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; - Marshal.StructureToPtr(dumpBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - imageStream.Write(dumpMs.ToArray(), 0, (int)dumpMs.Length); - - index.RemoveAll(t => t.blockType == BlockType.DumpHardwareBlock && t.dataType == DataType.NoData); - - index.Add(idxEntry); - } - - // If we have CICM XML metadata, write it - if(CicmMetadata != null) - { - MemoryStream cicmMs = new MemoryStream(); - XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType)); - xmlSer.Serialize(cicmMs, CicmMetadata); - - idxEntry = new IndexEntry - { - blockType = BlockType.CicmBlock, - dataType = DataType.NoData, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CICM XML block to position {0}", - idxEntry.offset); - - CicmMetadataBlock cicmBlock = - new CicmMetadataBlock {identifier = BlockType.CicmBlock, length = (uint)cicmMs.Length}; - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); - structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; - Marshal.StructureToPtr(cicmBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - imageStream.Write(cicmMs.ToArray(), 0, (int)cicmMs.Length); - - index.RemoveAll(t => t.blockType == BlockType.CicmBlock && t.dataType == DataType.NoData); - - index.Add(idxEntry); - } - - // If we have checksums, write it to disk - if(md5Provider != null || sha1Provider != null || sha256Provider != null || spamsumProvider != null) - { - MemoryStream chkMs = new MemoryStream(); - ChecksumHeader chkHeader = new ChecksumHeader {identifier = BlockType.ChecksumBlock}; - - if(md5Provider != null) - { - byte[] md5 = md5Provider.Final(); - ChecksumEntry md5Entry = - new ChecksumEntry {type = ChecksumAlgorithm.Md5, length = (uint)md5.Length}; - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(md5Entry)); - structureBytes = new byte[Marshal.SizeOf(md5Entry)]; - Marshal.StructureToPtr(md5Entry, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - chkMs.Write(structureBytes, 0, structureBytes.Length); - chkMs.Write(md5, 0, md5.Length); - chkHeader.entries++; - } - - if(sha1Provider != null) - { - byte[] sha1 = sha1Provider.Final(); - ChecksumEntry sha1Entry = - new ChecksumEntry {type = ChecksumAlgorithm.Sha1, length = (uint)sha1.Length}; - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(sha1Entry)); - structureBytes = new byte[Marshal.SizeOf(sha1Entry)]; - Marshal.StructureToPtr(sha1Entry, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - chkMs.Write(structureBytes, 0, structureBytes.Length); - chkMs.Write(sha1, 0, sha1.Length); - chkHeader.entries++; - } - - if(sha256Provider != null) - { - byte[] sha256 = sha256Provider.Final(); - ChecksumEntry sha256Entry = - new ChecksumEntry {type = ChecksumAlgorithm.Sha256, length = (uint)sha256.Length}; - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(sha256Entry)); - structureBytes = new byte[Marshal.SizeOf(sha256Entry)]; - Marshal.StructureToPtr(sha256Entry, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - chkMs.Write(structureBytes, 0, structureBytes.Length); - chkMs.Write(sha256, 0, sha256.Length); - chkHeader.entries++; - } - - if(spamsumProvider != null) - { - byte[] spamsum = Encoding.ASCII.GetBytes(spamsumProvider.End()); - ChecksumEntry spamsumEntry = - new ChecksumEntry {type = ChecksumAlgorithm.SpamSum, length = (uint)spamsum.Length}; - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(spamsumEntry)); - structureBytes = new byte[Marshal.SizeOf(spamsumEntry)]; - Marshal.StructureToPtr(spamsumEntry, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - chkMs.Write(structureBytes, 0, structureBytes.Length); - chkMs.Write(spamsum, 0, spamsum.Length); - chkHeader.entries++; - } - - if(chkHeader.entries > 0) - { - chkHeader.length = (uint)chkMs.Length; - idxEntry = new IndexEntry - { - blockType = BlockType.ChecksumBlock, - dataType = DataType.NoData, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing checksum block to position {0}", - idxEntry.offset); - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(chkHeader)); - structureBytes = new byte[Marshal.SizeOf(chkHeader)]; - Marshal.StructureToPtr(chkHeader, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - imageStream.Write(chkMs.ToArray(), 0, (int)chkMs.Length); - - index.RemoveAll(t => t.blockType == BlockType.ChecksumBlock && t.dataType == DataType.NoData); - - index.Add(idxEntry); - } - } - - // If the DDT is in-memory, write it to disk - if(inMemoryDdt) - { - idxEntry = new IndexEntry - { - blockType = BlockType.DeDuplicationTable, - dataType = DataType.UserData, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing user data DDT to position {0}", - idxEntry.offset); - - DdtHeader ddtHeader = new DdtHeader - { - identifier = BlockType.DeDuplicationTable, - type = DataType.UserData, - compression = CompressionType.Lzma, - shift = shift, - entries = (ulong)userDataDdt.LongLength, - length = (ulong)(userDataDdt.LongLength * sizeof(ulong)) - }; - - blockStream = new MemoryStream(); - lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); - crc64 = new Crc64Context(); - for(ulong i = 0; i < (ulong)userDataDdt.LongLength; i++) - { - byte[] ddtEntry = BitConverter.GetBytes(userDataDdt[i]); - crc64.Update(ddtEntry); - lzmaBlockStream.Write(ddtEntry, 0, ddtEntry.Length); - } - - byte[] lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - ddtHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; - Crc64Context cmpCrc64Context = new Crc64Context(); - cmpCrc64Context.Update(lzmaProperties); - cmpCrc64Context.Update(blockStream.ToArray()); - ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); - structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; - Marshal.StructureToPtr(ddtHeader, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - structureBytes = null; - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - blockStream = null; - lzmaBlockStream = null; - - index.RemoveAll(t => t.blockType == BlockType.DeDuplicationTable && t.dataType == DataType.UserData); - - index.Add(idxEntry); - } - - // Write the sector prefix, suffix and subchannels if present - switch(imageInfo.XmlMediaType) - { - case XmlMediaType.OpticalDisc when Tracks != null && Tracks.Count > 0: - DateTime startCompress; - DateTime endCompress; - if(sectorPrefix != null && sectorSuffix != null) - { - idxEntry = new IndexEntry - { - blockType = BlockType.DataBlock, - dataType = DataType.CdSectorPrefix, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Writing CD sector prefix block to position {0}", idxEntry.offset); - - Crc64Context.Data(sectorPrefix, out byte[] blockCrc); - - BlockHeader prefixBlock = new BlockHeader - { - identifier = BlockType.DataBlock, - type = DataType.CdSectorPrefix, - length = (uint)sectorPrefix.Length, - crc64 = BitConverter.ToUInt64(blockCrc, 0), - sectorSize = 16 - }; - - byte[] lzmaProperties = null; - - if(nocompress) - { - prefixBlock.compression = CompressionType.None; - prefixBlock.cmpCrc64 = prefixBlock.crc64; - prefixBlock.cmpLength = prefixBlock.length; - blockStream = new MemoryStream(sectorPrefix); - } - else - { - startCompress = DateTime.Now; - blockStream = new MemoryStream(); - lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); - lzmaBlockStream.Write(sectorPrefix, 0, sectorPrefix.Length); - lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - - Crc64Context cmpCrc = new Crc64Context(); - cmpCrc.Update(lzmaProperties); - cmpCrc.Update(blockStream.ToArray()); - blockCrc = cmpCrc.Final(); - prefixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; - prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); - prefixBlock.compression = CompressionType.Lzma; - - lzmaBlockStream = null; - endCompress = DateTime.Now; - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Took {0} seconds to compress prefix", - (endCompress - startCompress).TotalSeconds); - } - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(prefixBlock)); - structureBytes = new byte[Marshal.SizeOf(prefixBlock)]; - Marshal.StructureToPtr(prefixBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - if(prefixBlock.compression == CompressionType.Lzma) - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - - index.RemoveAll(t => t.blockType == BlockType.DataBlock && - t.dataType == DataType.CdSectorPrefix); - - index.Add(idxEntry); - - idxEntry = new IndexEntry - { - blockType = BlockType.DataBlock, - dataType = DataType.CdSectorSuffix, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Writing CD sector suffix block to position {0}", idxEntry.offset); - - Crc64Context.Data(sectorSuffix, out blockCrc); - - prefixBlock = new BlockHeader - { - identifier = BlockType.DataBlock, - type = DataType.CdSectorSuffix, - length = (uint)sectorSuffix.Length, - crc64 = BitConverter.ToUInt64(blockCrc, 0), - sectorSize = 288 - }; - - if(nocompress) - { - prefixBlock.compression = CompressionType.None; - prefixBlock.cmpCrc64 = prefixBlock.crc64; - prefixBlock.cmpLength = prefixBlock.length; - blockStream = new MemoryStream(sectorSuffix); - } - else - { - startCompress = DateTime.Now; - blockStream = new MemoryStream(); - lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); - lzmaBlockStream.Write(sectorSuffix, 0, sectorSuffix.Length); - lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - - Crc64Context cmpCrc = new Crc64Context(); - cmpCrc.Update(lzmaProperties); - cmpCrc.Update(blockStream.ToArray()); - blockCrc = cmpCrc.Final(); - prefixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; - prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); - prefixBlock.compression = CompressionType.Lzma; - - lzmaBlockStream = null; - endCompress = DateTime.Now; - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Took {0} seconds to compress suffix", - (endCompress - startCompress).TotalSeconds); - } - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(prefixBlock)); - structureBytes = new byte[Marshal.SizeOf(prefixBlock)]; - Marshal.StructureToPtr(prefixBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - if(prefixBlock.compression == CompressionType.Lzma) - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - - index.RemoveAll(t => t.blockType == BlockType.DataBlock && - t.dataType == DataType.CdSectorSuffix); - - index.Add(idxEntry); - blockStream = null; - } - - if(sectorSubchannel != null) - { - idxEntry = new IndexEntry - { - blockType = BlockType.DataBlock, - dataType = DataType.CdSectorSubchannel, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Writing CD subchannel block to position {0}", idxEntry.offset); - - Crc64Context.Data(sectorSubchannel, out byte[] blockCrc); - - BlockHeader subchannelBlock = new BlockHeader - { - identifier = BlockType.DataBlock, - type = DataType.CdSectorSubchannel, - length = (uint)sectorSubchannel.Length, - crc64 = BitConverter.ToUInt64(blockCrc, 0), - sectorSize = 96 - }; - - byte[] lzmaProperties = null; - - if(nocompress) - { - subchannelBlock.compression = CompressionType.None; - subchannelBlock.cmpCrc64 = subchannelBlock.crc64; - subchannelBlock.cmpLength = subchannelBlock.length; - blockStream = new MemoryStream(sectorSubchannel); - } - else - { - startCompress = DateTime.Now; - byte[] transformedSubchannel = ClauniaSubchannelTransform(sectorSubchannel); - blockStream = new MemoryStream(); - lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); - lzmaBlockStream.Write(transformedSubchannel, 0, transformedSubchannel.Length); - lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - - Crc64Context cmpCrc = new Crc64Context(); - cmpCrc.Update(lzmaProperties); - cmpCrc.Update(blockStream.ToArray()); - blockCrc = cmpCrc.Final(); - subchannelBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; - subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); - subchannelBlock.compression = CompressionType.LzmaClauniaSubchannelTransform; - - lzmaBlockStream = null; - endCompress = DateTime.Now; - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Took {0} seconds to compress subchannel", - (endCompress - startCompress).TotalSeconds); - } - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(subchannelBlock)); - structureBytes = new byte[Marshal.SizeOf(subchannelBlock)]; - Marshal.StructureToPtr(subchannelBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - if(subchannelBlock.compression == CompressionType.Lzma || subchannelBlock.compression == - CompressionType.LzmaClauniaSubchannelTransform) - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - - index.RemoveAll(t => t.blockType == BlockType.DataBlock && - t.dataType == DataType.CdSectorSubchannel); - - index.Add(idxEntry); - blockStream = null; - } - - List<TrackEntry> trackEntries = new List<TrackEntry>(); - foreach(Track track in Tracks) - { - trackFlags.TryGetValue((byte)track.TrackSequence, out byte flags); - trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc); - - if((flags & (int)CdFlags.DataTrack) == 0 && track.TrackType != TrackType.Audio) - flags += (byte)CdFlags.DataTrack; - - trackEntries.Add(new TrackEntry - { - sequence = (byte)track.TrackSequence, - type = track.TrackType, - start = (long)track.TrackStartSector, - end = (long)track.TrackEndSector, - pregap = (long)track.TrackPregap, - session = (byte)track.TrackSession, - isrc = isrc, - flags = flags - }); - } - - // If there are tracks build the tracks block - if(trackEntries.Count > 0) - { - blockStream = new MemoryStream(); - - foreach(TrackEntry entry in trackEntries) - { - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); - structureBytes = new byte[Marshal.SizeOf(entry)]; - Marshal.StructureToPtr(entry, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - blockStream.Write(structureBytes, 0, structureBytes.Length); - } - - Crc64Context.Data(blockStream.ToArray(), out byte[] trksCrc); - TracksHeader trkHeader = new TracksHeader - { - identifier = BlockType.TracksBlock, - entries = (ushort)trackEntries.Count, - crc64 = BitConverter.ToUInt64(trksCrc, 0) - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tracks to position {0}", - imageStream.Position); - - index.RemoveAll(t => t.blockType == BlockType.TracksBlock && t.dataType == DataType.NoData); - - index.Add(new IndexEntry - { - blockType = BlockType.TracksBlock, - dataType = DataType.NoData, - offset = (ulong)imageStream.Position - }); - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trkHeader)); - structureBytes = new byte[Marshal.SizeOf(trkHeader)]; - Marshal.StructureToPtr(trkHeader, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - } - - break; - case XmlMediaType.BlockMedia: - if(sectorSubchannel != null && (imageInfo.MediaType == MediaType.AppleFileWare || - imageInfo.MediaType == MediaType.AppleSonySS || - imageInfo.MediaType == MediaType.AppleSonyDS || - imageInfo.MediaType == MediaType.AppleProfile || - imageInfo.MediaType == MediaType.AppleWidget || - imageInfo.MediaType == MediaType.PriamDataTower)) - { - DataType tagType = DataType.NoData; - - switch(imageInfo.MediaType) - { - case MediaType.AppleSonySS: - case MediaType.AppleSonyDS: - tagType = DataType.AppleSonyTag; - break; - case MediaType.AppleFileWare: - case MediaType.AppleProfile: - case MediaType.AppleWidget: - tagType = DataType.AppleProfileTag; - break; - case MediaType.PriamDataTower: - tagType = DataType.PriamDataTowerTag; - break; - } - - idxEntry = new IndexEntry - { - blockType = BlockType.DataBlock, - dataType = tagType, - offset = (ulong)imageStream.Position - }; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", - "Writing apple sector tag block to position {0}", idxEntry.offset); - - Crc64Context.Data(sectorSubchannel, out byte[] blockCrc); - - BlockHeader subchannelBlock = new BlockHeader - { - identifier = BlockType.DataBlock, - type = tagType, - length = (uint)sectorSubchannel.Length, - crc64 = BitConverter.ToUInt64(blockCrc, 0) - }; - - switch(imageInfo.MediaType) - { - case MediaType.AppleSonySS: - case MediaType.AppleSonyDS: - subchannelBlock.sectorSize = 12; - break; - case MediaType.AppleFileWare: - case MediaType.AppleProfile: - case MediaType.AppleWidget: - subchannelBlock.sectorSize = 20; - break; - case MediaType.PriamDataTower: - subchannelBlock.sectorSize = 24; - break; - } - - byte[] lzmaProperties = null; - - if(nocompress) - { - subchannelBlock.compression = CompressionType.None; - subchannelBlock.cmpCrc64 = subchannelBlock.crc64; - subchannelBlock.cmpLength = subchannelBlock.length; - blockStream = new MemoryStream(sectorSubchannel); - } - else - { - blockStream = new MemoryStream(); - lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); - lzmaBlockStream.Write(sectorSubchannel, 0, sectorSubchannel.Length); - lzmaProperties = lzmaBlockStream.Properties; - lzmaBlockStream.Close(); - - Crc64Context cmpCrc = new Crc64Context(); - cmpCrc.Update(lzmaProperties); - cmpCrc.Update(blockStream.ToArray()); - blockCrc = cmpCrc.Final(); - subchannelBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; - subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); - subchannelBlock.compression = CompressionType.Lzma; - - lzmaBlockStream = null; - } - - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(subchannelBlock)); - structureBytes = new byte[Marshal.SizeOf(subchannelBlock)]; - Marshal.StructureToPtr(subchannelBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - if(subchannelBlock.compression == CompressionType.Lzma) - imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - - index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == tagType); - - index.Add(idxEntry); - blockStream = null; - } - - break; - } - - // Write metadata if present - SetMetadataFromTags(); - MetadataBlock metadataBlock = new MetadataBlock(); - blockStream = new MemoryStream(); - blockStream.Write(new byte[Marshal.SizeOf(metadataBlock)], 0, Marshal.SizeOf(metadataBlock)); - byte[] tmpUtf16Le; - - if(imageInfo.MediaSequence > 0 && imageInfo.LastMediaSequence > 0) - { - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.mediaSequence = imageInfo.MediaSequence; - metadataBlock.lastMediaSequence = imageInfo.LastMediaSequence; - } - - if(!string.IsNullOrWhiteSpace(imageInfo.Creator)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.Creator); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.creatorOffset = (uint)blockStream.Position; - metadataBlock.creatorLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.Comments)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.Comments); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.commentsOffset = (uint)blockStream.Position; - metadataBlock.commentsLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.MediaTitle)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaTitle); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.mediaTitleOffset = (uint)blockStream.Position; - metadataBlock.mediaTitleLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.MediaManufacturer)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaManufacturer); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.mediaManufacturerOffset = (uint)blockStream.Position; - metadataBlock.mediaManufacturerLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.MediaModel)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaModel); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.mediaModelOffset = (uint)blockStream.Position; - metadataBlock.mediaModelLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.MediaSerialNumber)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaSerialNumber); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.mediaSerialNumberOffset = (uint)blockStream.Position; - metadataBlock.mediaSerialNumberLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.MediaBarcode)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaBarcode); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.mediaBarcodeOffset = (uint)blockStream.Position; - metadataBlock.mediaBarcodeLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.MediaPartNumber)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaPartNumber); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.mediaPartNumberOffset = (uint)blockStream.Position; - metadataBlock.mediaPartNumberLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveManufacturer); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.driveManufacturerOffset = (uint)blockStream.Position; - metadataBlock.driveManufacturerLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.DriveModel)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveModel); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.driveModelOffset = (uint)blockStream.Position; - metadataBlock.driveModelLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveSerialNumber); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.driveSerialNumberOffset = (uint)blockStream.Position; - metadataBlock.driveSerialNumberLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - if(!string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) - { - tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveFirmwareRevision); - metadataBlock.identifier = BlockType.MetadataBlock; - metadataBlock.driveFirmwareRevisionOffset = (uint)blockStream.Position; - metadataBlock.driveFirmwareRevisionLength = (uint)(tmpUtf16Le.Length + 2); - blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); - blockStream.Write(new byte[] {0, 0}, 0, 2); - } - - // Check if we set up any metadata earlier, then write its block - if(metadataBlock.identifier == BlockType.MetadataBlock) - { - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing metadata to position {0}", - imageStream.Position); - metadataBlock.blockSize = (uint)blockStream.Length; - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(metadataBlock)); - structureBytes = new byte[Marshal.SizeOf(metadataBlock)]; - Marshal.StructureToPtr(metadataBlock, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - blockStream.Position = 0; - blockStream.Write(structureBytes, 0, structureBytes.Length); - index.RemoveAll(t => t.blockType == BlockType.MetadataBlock && t.dataType == DataType.NoData); - - index.Add(new IndexEntry - { - blockType = BlockType.MetadataBlock, - dataType = DataType.NoData, - offset = (ulong)imageStream.Position - }); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - } - - header.indexOffset = (ulong)imageStream.Position; - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing index to position {0}", - header.indexOffset); - - blockStream = new MemoryStream(); - - // Write index to memory - foreach(IndexEntry entry in index) - { - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); - structureBytes = new byte[Marshal.SizeOf(entry)]; - Marshal.StructureToPtr(entry, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - blockStream.Write(structureBytes, 0, structureBytes.Length); - } - - Crc64Context.Data(blockStream.ToArray(), out byte[] idxCrc); - - IndexHeader idxHeader = new IndexHeader - { - identifier = BlockType.Index, - entries = (ushort)index.Count, - crc64 = BitConverter.ToUInt64(idxCrc, 0) - }; - - // Write index to disk - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); - structureBytes = new byte[Marshal.SizeOf(idxHeader)]; - Marshal.StructureToPtr(idxHeader, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing header"); - header.lastWrittenTime = DateTime.UtcNow.ToFileTimeUtc(); - imageStream.Position = 0; - structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - structureBytes = new byte[Marshal.SizeOf(header)]; - Marshal.StructureToPtr(header, structurePointer, true); - Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); - Marshal.FreeHGlobal(structurePointer); - imageStream.Write(structureBytes, 0, structureBytes.Length); - - imageStream.Flush(); - imageStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.Creator = metadata.Creator; - imageInfo.Comments = metadata.Comments; - imageInfo.MediaManufacturer = metadata.MediaManufacturer; - imageInfo.MediaModel = metadata.MediaModel; - imageInfo.MediaSerialNumber = metadata.MediaSerialNumber; - imageInfo.MediaBarcode = metadata.MediaBarcode; - imageInfo.MediaPartNumber = metadata.MediaPartNumber; - imageInfo.MediaSequence = metadata.MediaSequence; - imageInfo.LastMediaSequence = metadata.LastMediaSequence; - imageInfo.DriveManufacturer = metadata.DriveManufacturer; - imageInfo.DriveModel = metadata.DriveModel; - imageInfo.DriveSerialNumber = metadata.DriveSerialNumber; - imageInfo.DriveFirmwareRevision = metadata.DriveFirmwareRevision; - imageInfo.MediaTitle = metadata.MediaTitle; - - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(imageInfo.XmlMediaType != XmlMediaType.BlockMedia) - { - ErrorMessage = "Tried to set geometry on a media that doesn't suppport it"; - return false; - } - - geometryBlock = new GeometryBlock - { - identifier = BlockType.GeometryBlock, - cylinders = cylinders, - heads = heads, - sectorsPerTrack = sectorsPerTrack - }; - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - Track track = new Track(); - switch(tag) - { - case SectorTagType.CdTrackFlags: - case SectorTagType.CdTrackIsrc: - case SectorTagType.CdSectorSubchannel: - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - { - ErrorMessage = "Incorrect tag for disk type"; - return false; - } - - track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && - sectorAddress <= trk.TrackEndSector); - if(track.TrackSequence == 0) - { - ErrorMessage = $"Can't found track containing {sectorAddress}"; - return false; - } - - break; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - { - if(data.Length != 1) - { - ErrorMessage = "Incorrect data size for track flags"; - return false; - } - - trackFlags.Add((byte)track.TrackSequence, data[0]); - - return true; - } - case SectorTagType.CdTrackIsrc: - { - if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); - return true; - } - case SectorTagType.CdSectorSubchannel: - { - if(data.Length != 96) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - if(sectorSubchannel == null) sectorSubchannel = new byte[imageInfo.Sectors * 96]; - - Array.Copy(data, 0, sectorSubchannel, (int)(96 * sectorAddress), 96); - - return true; - } - default: - ErrorMessage = $"Don't know how to write sector tag type {tag}"; - return false; - } - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - switch(tag) - { - case SectorTagType.CdTrackFlags: - case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); - case SectorTagType.CdSectorSubchannel: - { - if(data.Length % 96 != 0) - { - ErrorMessage = "Incorrect data size for subchannel"; - return false; - } - - if(sectorSubchannel == null) sectorSubchannel = new byte[imageInfo.Sectors * 96]; - - if(sectorAddress * 96 + length * 96 > (ulong)sectorSubchannel.LongLength) - { - ErrorMessage = "Tried to write more data than possible"; - return false; - } - - Array.Copy(data, 0, sectorSubchannel, (int)(96 * sectorAddress), 96 * length); - - return true; - } - - default: - ErrorMessage = $"Don't know how to write sector tag type {tag}"; - return false; - } - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - DumpHardware = dumpHardware; - return true; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - CicmMetadata = metadata; - return true; - } - - /// <summary> - /// Checks for media tags that may contain metadata and sets it up if not already set - /// </summary> - void SetMetadataFromTags() - { - // Search for SecureDigital CID - if(mediaTags.TryGetValue(MediaTagType.SD_CID, out byte[] sdCid)) - { - CID decoded = Decoders.SecureDigital.Decoders.DecodeCID(sdCid); - if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) - imageInfo.DriveManufacturer = VendorString.Prettify(decoded.Manufacturer); - if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) imageInfo.DriveModel = decoded.ProductName; - if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) - imageInfo.DriveFirmwareRevision = - $"{(decoded.ProductRevision & 0xF0) >> 4:X2}.{decoded.ProductRevision & 0x0F:X2}"; - if(string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) - imageInfo.DriveSerialNumber = $"{decoded.ProductSerialNumber}"; - } - - // Search for MultiMediaCard CID - if(mediaTags.TryGetValue(MediaTagType.MMC_CID, out byte[] mmcCid)) - { - Decoders.MMC.CID decoded = Decoders.MMC.Decoders.DecodeCID(mmcCid); - if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) - imageInfo.DriveManufacturer = Decoders.MMC.VendorString.Prettify(decoded.Manufacturer); - if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) imageInfo.DriveModel = decoded.ProductName; - if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) - imageInfo.DriveFirmwareRevision = - $"{(decoded.ProductRevision & 0xF0) >> 4:X2}.{decoded.ProductRevision & 0x0F:X2}"; - if(string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) - imageInfo.DriveSerialNumber = $"{decoded.ProductSerialNumber}"; - } - - // Search for SCSI INQUIRY - if(mediaTags.TryGetValue(MediaTagType.SCSI_INQUIRY, out byte[] scsiInquiry)) - { - Inquiry.SCSIInquiry? nullableInquiry = Inquiry.Decode(scsiInquiry); - - if(nullableInquiry.HasValue) - { - Inquiry.SCSIInquiry inquiry = nullableInquiry.Value; - if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) - imageInfo.DriveManufacturer = StringHandlers.CToString(inquiry.VendorIdentification)?.Trim(); - if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) - imageInfo.DriveModel = StringHandlers.CToString(inquiry.ProductIdentification)?.Trim(); - if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) - imageInfo.DriveFirmwareRevision = - StringHandlers.CToString(inquiry.ProductRevisionLevel)?.Trim(); - } - } - - // Search for ATA or ATAPI IDENTIFY - if(!mediaTags.TryGetValue(MediaTagType.ATA_IDENTIFY, out byte[] ataIdentify) && - !mediaTags.TryGetValue(MediaTagType.ATAPI_IDENTIFY, out ataIdentify)) return; - - Identify.IdentifyDevice? nullableIdentify = Decoders.ATA.Identify.Decode(ataIdentify); - - if(!nullableIdentify.HasValue) return; - - Identify.IdentifyDevice identify = nullableIdentify.Value; - - string[] separated = identify.Model.Split(' '); - - if(separated.Length == 1) - if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) - imageInfo.DriveModel = separated[0]; - else - { - if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) - imageInfo.DriveManufacturer = separated[0]; - if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) - imageInfo.DriveModel = separated[separated.Length - 1]; - } - - if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) - imageInfo.DriveFirmwareRevision = identify.FirmwareRevision; - if(string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) - imageInfo.DriveSerialNumber = identify.SerialNumber; - } - - // Get the CICM XML media type from DIC media type - static XmlMediaType GetXmlMediaType(MediaType type) - { - switch(type) - { - case MediaType.CD: - case MediaType.CDDA: - case MediaType.CDG: - case MediaType.CDEG: - case MediaType.CDI: - case MediaType.CDROM: - case MediaType.CDROMXA: - case MediaType.CDPLUS: - case MediaType.CDMO: - case MediaType.CDR: - case MediaType.CDRW: - case MediaType.CDMRW: - case MediaType.VCD: - case MediaType.SVCD: - case MediaType.PCD: - case MediaType.SACD: - case MediaType.DDCD: - case MediaType.DDCDR: - case MediaType.DDCDRW: - case MediaType.DTSCD: - case MediaType.CDMIDI: - case MediaType.CDV: - case MediaType.DVDROM: - case MediaType.DVDR: - case MediaType.DVDRW: - case MediaType.DVDPR: - case MediaType.DVDPRW: - case MediaType.DVDPRWDL: - case MediaType.DVDRDL: - case MediaType.DVDPRDL: - case MediaType.DVDRAM: - case MediaType.DVDRWDL: - case MediaType.DVDDownload: - case MediaType.HDDVDROM: - case MediaType.HDDVDRAM: - case MediaType.HDDVDR: - case MediaType.HDDVDRW: - case MediaType.HDDVDRDL: - case MediaType.HDDVDRWDL: - case MediaType.BDROM: - case MediaType.BDR: - case MediaType.BDRE: - case MediaType.BDRXL: - case MediaType.BDREXL: - case MediaType.EVD: - case MediaType.FVD: - case MediaType.HVD: - case MediaType.CBHD: - case MediaType.HDVMD: - case MediaType.VCDHD: - case MediaType.SVOD: - case MediaType.FDDVD: - case MediaType.LD: - case MediaType.LDROM: - case MediaType.LDROM2: - case MediaType.LVROM: - case MediaType.MegaLD: - case MediaType.PS1CD: - case MediaType.PS2CD: - case MediaType.PS2DVD: - case MediaType.PS3DVD: - case MediaType.PS3BD: - case MediaType.PS4BD: - case MediaType.UMD: - case MediaType.XGD: - case MediaType.XGD2: - case MediaType.XGD3: - case MediaType.XGD4: - case MediaType.MEGACD: - case MediaType.SATURNCD: - case MediaType.GDROM: - case MediaType.GDR: - case MediaType.SuperCDROM2: - case MediaType.JaguarCD: - case MediaType.ThreeDO: - case MediaType.PCFX: - case MediaType.NeoGeoCD: - case MediaType.GOD: - case MediaType.WOD: - case MediaType.WUOD: return XmlMediaType.OpticalDisc; - default: return XmlMediaType.BlockMedia; - } - } - - // Gets a DDT entry - ulong GetDdtEntry(ulong sectorAddress) - { - if(inMemoryDdt) return userDataDdt[sectorAddress]; - - if(ddtEntryCache.TryGetValue(sectorAddress, out ulong entry)) return entry; - - long oldPosition = imageStream.Position; - imageStream.Position = outMemoryDdtPosition + Marshal.SizeOf(typeof(DdtHeader)); - imageStream.Position += (long)(sectorAddress * sizeof(ulong)); - byte[] temp = new byte[sizeof(ulong)]; - imageStream.Read(temp, 0, sizeof(ulong)); - imageStream.Position = oldPosition; - entry = BitConverter.ToUInt64(temp, 0); - - if(ddtEntryCache.Count >= MAX_DDT_ENTRY_CACHE) ddtEntryCache.Clear(); - - ddtEntryCache.Add(sectorAddress, entry); - return entry; - } - - // Sets a DDT entry - void SetDdtEntry(ulong sectorAddress, ulong pointer) - { - if(inMemoryDdt) - { - userDataDdt[sectorAddress] = pointer; - return; - } - - long oldPosition = imageStream.Position; - imageStream.Position = outMemoryDdtPosition + Marshal.SizeOf(typeof(DdtHeader)); - imageStream.Position += (long)(sectorAddress * sizeof(ulong)); - imageStream.Write(BitConverter.GetBytes(pointer), 0, sizeof(ulong)); - imageStream.Position = oldPosition; - } - - // Converts between image data type and dic media tag type - static MediaTagType GetMediaTagTypeForDataType(DataType type) - { - switch(type) - { - case DataType.CompactDiscPartialToc: return MediaTagType.CD_TOC; - case DataType.CompactDiscSessionInfo: return MediaTagType.CD_SessionInfo; - case DataType.CompactDiscToc: return MediaTagType.CD_FullTOC; - case DataType.CompactDiscPma: return MediaTagType.CD_PMA; - case DataType.CompactDiscAtip: return MediaTagType.CD_ATIP; - case DataType.CompactDiscLeadInCdText: return MediaTagType.CD_TEXT; - case DataType.DvdPfi: return MediaTagType.DVD_PFI; - case DataType.DvdLeadInCmi: return MediaTagType.DVD_CMI; - case DataType.DvdDiscKey: return MediaTagType.DVD_DiscKey; - case DataType.DvdBca: return MediaTagType.DVD_BCA; - case DataType.DvdDmi: return MediaTagType.DVD_DMI; - case DataType.DvdMediaIdentifier: return MediaTagType.DVD_MediaIdentifier; - case DataType.DvdMediaKeyBlock: return MediaTagType.DVD_MKB; - case DataType.DvdRamDds: return MediaTagType.DVDRAM_DDS; - case DataType.DvdRamMediumStatus: return MediaTagType.DVDRAM_MediumStatus; - case DataType.DvdRamSpareArea: return MediaTagType.DVDRAM_SpareArea; - case DataType.DvdRRmd: return MediaTagType.DVDR_RMD; - case DataType.DvdRPrerecordedInfo: return MediaTagType.DVDR_PreRecordedInfo; - case DataType.DvdRMediaIdentifier: return MediaTagType.DVDR_MediaIdentifier; - case DataType.DvdRPfi: return MediaTagType.DVDR_PFI; - case DataType.DvdAdip: return MediaTagType.DVD_ADIP; - case DataType.HdDvdCpi: return MediaTagType.HDDVD_CPI; - case DataType.HdDvdMediumStatus: return MediaTagType.HDDVD_MediumStatus; - case DataType.DvdDlLayerCapacity: return MediaTagType.DVDDL_LayerCapacity; - case DataType.DvdDlMiddleZoneAddress: return MediaTagType.DVDDL_MiddleZoneAddress; - case DataType.DvdDlJumpIntervalSize: return MediaTagType.DVDDL_JumpIntervalSize; - case DataType.DvdDlManualLayerJumpLba: return MediaTagType.DVDDL_ManualLayerJumpLBA; - case DataType.BlurayDi: return MediaTagType.BD_DI; - case DataType.BlurayBca: return MediaTagType.BD_BCA; - case DataType.BlurayDds: return MediaTagType.BD_DDS; - case DataType.BlurayCartridgeStatus: return MediaTagType.BD_CartridgeStatus; - case DataType.BluraySpareArea: return MediaTagType.BD_SpareArea; - case DataType.AacsVolumeIdentifier: return MediaTagType.AACS_VolumeIdentifier; - case DataType.AacsSerialNumber: return MediaTagType.AACS_SerialNumber; - case DataType.AacsMediaIdentifier: return MediaTagType.AACS_MediaIdentifier; - case DataType.AacsMediaKeyBlock: return MediaTagType.AACS_MKB; - case DataType.AacsDataKeys: return MediaTagType.AACS_DataKeys; - case DataType.AacsLbaExtents: return MediaTagType.AACS_LBAExtents; - case DataType.CprmMediaKeyBlock: return MediaTagType.AACS_CPRM_MKB; - case DataType.HybridRecognizedLayers: return MediaTagType.Hybrid_RecognizedLayers; - case DataType.ScsiMmcWriteProtection: return MediaTagType.MMC_WriteProtection; - case DataType.ScsiMmcDiscInformation: return MediaTagType.MMC_DiscInformation; - case DataType.ScsiMmcTrackResourcesInformation: return MediaTagType.MMC_TrackResourcesInformation; - case DataType.ScsiMmcPowResourcesInformation: return MediaTagType.MMC_POWResourcesInformation; - case DataType.ScsiInquiry: return MediaTagType.SCSI_INQUIRY; - case DataType.ScsiModePage2A: return MediaTagType.SCSI_MODEPAGE_2A; - case DataType.AtaIdentify: return MediaTagType.ATA_IDENTIFY; - case DataType.AtapiIdentify: return MediaTagType.ATAPI_IDENTIFY; - case DataType.PcmciaCis: return MediaTagType.PCMCIA_CIS; - case DataType.SecureDigitalCid: return MediaTagType.SD_CID; - case DataType.SecureDigitalCsd: return MediaTagType.SD_CSD; - case DataType.SecureDigitalScr: return MediaTagType.SD_SCR; - case DataType.SecureDigitalOcr: return MediaTagType.SD_OCR; - case DataType.MultiMediaCardCid: return MediaTagType.MMC_CID; - case DataType.MultiMediaCardCsd: return MediaTagType.MMC_CSD; - case DataType.MultiMediaCardOcr: return MediaTagType.MMC_OCR; - case DataType.MultiMediaCardExtendedCsd: return MediaTagType.MMC_ExtendedCSD; - case DataType.XboxSecuritySector: return MediaTagType.Xbox_SecuritySector; - case DataType.FloppyLeadOut: return MediaTagType.Floppy_LeadOut; - case DataType.DvdDiscControlBlock: return MediaTagType.DCB; - case DataType.CompactDiscLeadIn: return MediaTagType.CD_LeadIn; - case DataType.CompactDiscLeadOut: return MediaTagType.CD_LeadOut; - case DataType.ScsiModeSense6: return MediaTagType.SCSI_MODESENSE_6; - case DataType.ScsiModeSense10: return MediaTagType.SCSI_MODESENSE_10; - case DataType.UsbDescriptors: return MediaTagType.USB_Descriptors; - case DataType.XboxDmi: return MediaTagType.Xbox_DMI; - case DataType.XboxPfi: return MediaTagType.Xbox_PFI; - case DataType.CompactDiscMediaCatalogueNumber: return MediaTagType.CD_MCN; - default: throw new ArgumentOutOfRangeException(); - } - } - - // Converts between dic media tag type and image data type - static DataType GetDataTypeForMediaTag(MediaTagType tag) - { - switch(tag) - { - case MediaTagType.CD_TOC: return DataType.CompactDiscPartialToc; - case MediaTagType.CD_SessionInfo: return DataType.CompactDiscSessionInfo; - case MediaTagType.CD_FullTOC: return DataType.CompactDiscToc; - case MediaTagType.CD_PMA: return DataType.CompactDiscPma; - case MediaTagType.CD_ATIP: return DataType.CompactDiscAtip; - case MediaTagType.CD_TEXT: return DataType.CompactDiscLeadInCdText; - case MediaTagType.DVD_PFI: return DataType.DvdPfi; - case MediaTagType.DVD_CMI: return DataType.DvdLeadInCmi; - case MediaTagType.DVD_DiscKey: return DataType.DvdDiscKey; - case MediaTagType.DVD_BCA: return DataType.DvdBca; - case MediaTagType.DVD_DMI: return DataType.DvdDmi; - case MediaTagType.DVD_MediaIdentifier: return DataType.DvdMediaIdentifier; - case MediaTagType.DVD_MKB: return DataType.DvdMediaKeyBlock; - case MediaTagType.DVDRAM_DDS: return DataType.DvdRamDds; - case MediaTagType.DVDRAM_MediumStatus: return DataType.DvdRamMediumStatus; - case MediaTagType.DVDRAM_SpareArea: return DataType.DvdRamSpareArea; - case MediaTagType.DVDR_RMD: return DataType.DvdRRmd; - case MediaTagType.DVDR_PreRecordedInfo: return DataType.DvdRPrerecordedInfo; - case MediaTagType.DVDR_MediaIdentifier: return DataType.DvdRMediaIdentifier; - case MediaTagType.DVDR_PFI: return DataType.DvdRPfi; - case MediaTagType.DVD_ADIP: return DataType.DvdAdip; - case MediaTagType.HDDVD_CPI: return DataType.HdDvdCpi; - case MediaTagType.HDDVD_MediumStatus: return DataType.HdDvdMediumStatus; - case MediaTagType.DVDDL_LayerCapacity: return DataType.DvdDlLayerCapacity; - case MediaTagType.DVDDL_MiddleZoneAddress: return DataType.DvdDlMiddleZoneAddress; - case MediaTagType.DVDDL_JumpIntervalSize: return DataType.DvdDlJumpIntervalSize; - case MediaTagType.DVDDL_ManualLayerJumpLBA: return DataType.DvdDlManualLayerJumpLba; - case MediaTagType.BD_DI: return DataType.BlurayDi; - case MediaTagType.BD_BCA: return DataType.BlurayBca; - case MediaTagType.BD_DDS: return DataType.BlurayDds; - case MediaTagType.BD_CartridgeStatus: return DataType.BlurayCartridgeStatus; - case MediaTagType.BD_SpareArea: return DataType.BluraySpareArea; - case MediaTagType.AACS_VolumeIdentifier: return DataType.AacsVolumeIdentifier; - case MediaTagType.AACS_SerialNumber: return DataType.AacsSerialNumber; - case MediaTagType.AACS_MediaIdentifier: return DataType.AacsMediaIdentifier; - case MediaTagType.AACS_MKB: return DataType.AacsMediaKeyBlock; - case MediaTagType.AACS_DataKeys: return DataType.AacsDataKeys; - case MediaTagType.AACS_LBAExtents: return DataType.AacsLbaExtents; - case MediaTagType.AACS_CPRM_MKB: return DataType.CprmMediaKeyBlock; - case MediaTagType.Hybrid_RecognizedLayers: return DataType.HybridRecognizedLayers; - case MediaTagType.MMC_WriteProtection: return DataType.ScsiMmcWriteProtection; - case MediaTagType.MMC_DiscInformation: return DataType.ScsiMmcDiscInformation; - case MediaTagType.MMC_TrackResourcesInformation: return DataType.ScsiMmcTrackResourcesInformation; - case MediaTagType.MMC_POWResourcesInformation: return DataType.ScsiMmcPowResourcesInformation; - case MediaTagType.SCSI_INQUIRY: return DataType.ScsiInquiry; - case MediaTagType.SCSI_MODEPAGE_2A: return DataType.ScsiModePage2A; - case MediaTagType.ATA_IDENTIFY: return DataType.AtaIdentify; - case MediaTagType.ATAPI_IDENTIFY: return DataType.AtapiIdentify; - case MediaTagType.PCMCIA_CIS: return DataType.PcmciaCis; - case MediaTagType.SD_CID: return DataType.SecureDigitalCid; - case MediaTagType.SD_CSD: return DataType.SecureDigitalCsd; - case MediaTagType.SD_SCR: return DataType.SecureDigitalScr; - case MediaTagType.SD_OCR: return DataType.SecureDigitalOcr; - case MediaTagType.MMC_CID: return DataType.MultiMediaCardCid; - case MediaTagType.MMC_CSD: return DataType.MultiMediaCardCsd; - case MediaTagType.MMC_OCR: return DataType.MultiMediaCardOcr; - case MediaTagType.MMC_ExtendedCSD: return DataType.MultiMediaCardExtendedCsd; - case MediaTagType.Xbox_SecuritySector: return DataType.XboxSecuritySector; - case MediaTagType.Floppy_LeadOut: return DataType.FloppyLeadOut; - case MediaTagType.DCB: return DataType.DvdDiscControlBlock; - case MediaTagType.CD_LeadIn: return DataType.CompactDiscLeadIn; - case MediaTagType.CD_LeadOut: return DataType.CompactDiscLeadOut; - case MediaTagType.SCSI_MODESENSE_6: return DataType.ScsiModeSense6; - case MediaTagType.SCSI_MODESENSE_10: return DataType.ScsiModeSense10; - case MediaTagType.USB_Descriptors: return DataType.UsbDescriptors; - case MediaTagType.Xbox_DMI: return DataType.XboxDmi; - case MediaTagType.Xbox_PFI: return DataType.XboxPfi; - case MediaTagType.CD_MCN: return DataType.CompactDiscMediaCatalogueNumber; - default: - throw new ArgumentOutOfRangeException(nameof(tag), tag, null); - } - } - - byte[] ClauniaSubchannelTransform(byte[] interleaved) - { - if(interleaved == null) return null; - - int[] p = new int[interleaved.Length / 8]; - int[] q = new int[interleaved.Length / 8]; - int[] r = new int[interleaved.Length / 8]; - int[] s = new int[interleaved.Length / 8]; - int[] t = new int[interleaved.Length / 8]; - int[] u = new int[interleaved.Length / 8]; - int[] v = new int[interleaved.Length / 8]; - int[] w = new int[interleaved.Length / 8]; - - DateTime start = DateTime.UtcNow; - for(int i = 0; i < interleaved.Length; i += 8) - { - p[i / 8] = interleaved[i] & 0x80; - p[i / 8] += (interleaved[i + 1] & 0x80) >> 1; - p[i / 8] += (interleaved[i + 2] & 0x80) >> 2; - p[i / 8] += (interleaved[i + 3] & 0x80) >> 3; - p[i / 8] += (interleaved[i + 4] & 0x80) >> 4; - p[i / 8] += (interleaved[i + 5] & 0x80) >> 5; - p[i / 8] += (interleaved[i + 6] & 0x80) >> 6; - p[i / 8] += (interleaved[i + 7] & 0x80) >> 7; - - q[i / 8] = (interleaved[i] & 0x40) << 1; - q[i / 8] += interleaved[i + 1] & 0x40; - q[i / 8] += (interleaved[i + 2] & 0x40) >> 1; - q[i / 8] += (interleaved[i + 3] & 0x40) >> 2; - q[i / 8] += (interleaved[i + 4] & 0x40) >> 3; - q[i / 8] += (interleaved[i + 5] & 0x40) >> 4; - q[i / 8] += (interleaved[i + 6] & 0x40) >> 5; - q[i / 8] += (interleaved[i + 7] & 0x40) >> 6; - - r[i / 8] = (interleaved[i] & 0x20) << 2; - r[i / 8] += (interleaved[i + 1] & 0x20) << 1; - r[i / 8] += interleaved[i + 2] & 0x20; - r[i / 8] += (interleaved[i + 3] & 0x20) >> 1; - r[i / 8] += (interleaved[i + 4] & 0x20) >> 2; - r[i / 8] += (interleaved[i + 5] & 0x20) >> 3; - r[i / 8] += (interleaved[i + 6] & 0x20) >> 4; - r[i / 8] += (interleaved[i + 7] & 0x20) >> 5; - - s[i / 8] = (interleaved[i] & 0x10) << 3; - s[i / 8] += (interleaved[i + 1] & 0x10) << 2; - s[i / 8] += (interleaved[i + 2] & 0x10) << 1; - s[i / 8] += interleaved[i + 3] & 0x10; - s[i / 8] += (interleaved[i + 4] & 0x10) >> 1; - s[i / 8] += (interleaved[i + 5] & 0x10) >> 2; - s[i / 8] += (interleaved[i + 6] & 0x10) >> 3; - s[i / 8] += (interleaved[i + 7] & 0x10) >> 4; - - t[i / 8] = (interleaved[i] & 0x08) << 4; - t[i / 8] += (interleaved[i + 1] & 0x08) << 3; - t[i / 8] += (interleaved[i + 2] & 0x08) << 2; - t[i / 8] += (interleaved[i + 3] & 0x08) << 1; - t[i / 8] += interleaved[i + 4] & 0x08; - t[i / 8] += (interleaved[i + 5] & 0x08) >> 1; - t[i / 8] += (interleaved[i + 6] & 0x08) >> 2; - t[i / 8] += (interleaved[i + 7] & 0x08) >> 3; - - u[i / 8] = (interleaved[i] & 0x04) << 5; - u[i / 8] += (interleaved[i + 1] & 0x04) << 4; - u[i / 8] += (interleaved[i + 2] & 0x04) << 3; - u[i / 8] += (interleaved[i + 3] & 0x04) << 2; - u[i / 8] += (interleaved[i + 4] & 0x04) << 1; - u[i / 8] += interleaved[i + 5] & 0x04; - u[i / 8] += (interleaved[i + 6] & 0x04) >> 1; - u[i / 8] += (interleaved[i + 7] & 0x04) >> 2; - - v[i / 8] = (interleaved[i] & 0x02) << 6; - v[i / 8] += (interleaved[i + 1] & 0x02) << 5; - v[i / 8] += (interleaved[i + 2] & 0x02) << 4; - v[i / 8] += (interleaved[i + 3] & 0x02) << 3; - v[i / 8] += (interleaved[i + 4] & 0x02) << 2; - v[i / 8] += (interleaved[i + 5] & 0x02) << 1; - v[i / 8] += interleaved[i + 6] & 0x02; - v[i / 8] += (interleaved[i + 7] & 0x02) >> 1; - - w[i / 8] = (interleaved[i] & 0x01) << 7; - w[i / 8] += (interleaved[i + 1] & 0x01) << 6; - w[i / 8] += (interleaved[i + 2] & 0x01) << 5; - w[i / 8] += (interleaved[i + 3] & 0x01) << 4; - w[i / 8] += (interleaved[i + 4] & 0x01) << 3; - w[i / 8] += (interleaved[i + 5] & 0x01) << 2; - w[i / 8] += (interleaved[i + 6] & 0x01) << 1; - w[i / 8] += interleaved[i + 7] & 0x01; - } - - DateTime end = DateTime.UtcNow; - TimeSpan deinterleave = end - start; - - byte[] sequential = new byte[interleaved.Length]; - start = DateTime.UtcNow; - - int qStart = p.Length * 1; - int rStart = p.Length * 2; - int sStart = p.Length * 3; - int tStart = p.Length * 4; - int uStart = p.Length * 5; - int vStart = p.Length * 6; - int wStart = p.Length * 7; - - for(int i = 0; i < p.Length; i++) - { - sequential[i] = (byte)p[i]; - sequential[qStart + i] = (byte)q[i]; - sequential[rStart + i] = (byte)r[i]; - sequential[sStart + i] = (byte)s[i]; - sequential[tStart + i] = (byte)t[i]; - sequential[uStart + i] = (byte)u[i]; - sequential[vStart + i] = (byte)v[i]; - sequential[wStart + i] = (byte)w[i]; - } - - end = DateTime.UtcNow; - TimeSpan sequentialize = end - start; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to deinterleave subchannel.", - deinterleave.TotalMilliseconds); - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to sequentialize subchannel.", - sequentialize.TotalMilliseconds); - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to transform subchannel.", - deinterleave.TotalMilliseconds + sequentialize.TotalMilliseconds); - - return sequential; - } - - byte[] ClauniaSubchannelUntransform(byte[] sequential) - { - if(sequential == null) return null; - - int[] p = new int[sequential.Length / 8]; - int[] q = new int[sequential.Length / 8]; - int[] r = new int[sequential.Length / 8]; - int[] s = new int[sequential.Length / 8]; - int[] t = new int[sequential.Length / 8]; - int[] u = new int[sequential.Length / 8]; - int[] v = new int[sequential.Length / 8]; - int[] w = new int[sequential.Length / 8]; - - int qStart = p.Length * 1; - int rStart = p.Length * 2; - int sStart = p.Length * 3; - int tStart = p.Length * 4; - int uStart = p.Length * 5; - int vStart = p.Length * 6; - int wStart = p.Length * 7; - - DateTime start = DateTime.UtcNow; - - for(int i = 0; i < p.Length; i++) - { - p[i] = sequential[i]; - q[i] = sequential[qStart + i]; - r[i] = sequential[rStart + i]; - s[i] = sequential[sStart + i]; - t[i] = sequential[tStart + i]; - u[i] = sequential[uStart + i]; - v[i] = sequential[vStart + i]; - w[i] = sequential[wStart + i]; - } - - DateTime end = DateTime.UtcNow; - TimeSpan desequentialize = end - start; - - byte[] interleaved = new byte[sequential.Length]; - start = DateTime.UtcNow; - for(int i = 0; i < interleaved.Length; i += 8) - { - interleaved[i] = (byte)((p[i / 8] & 0x80) == 0x80 ? 0x80 : 0); - interleaved[i + 1] += (byte)((p[i / 8] & 0x40) == 0x40 ? 0x80 : 0); - interleaved[i + 2] += (byte)((p[i / 8] & 0x20) == 0x20 ? 0x80 : 0); - interleaved[i + 3] += (byte)((p[i / 8] & 0x10) == 0x10 ? 0x80 : 0); - interleaved[i + 4] += (byte)((p[i / 8] & 0x08) == 0x08 ? 0x80 : 0); - interleaved[i + 5] += (byte)((p[i / 8] & 0x04) == 0x04 ? 0x80 : 0); - interleaved[i + 6] += (byte)((p[i / 8] & 0x02) == 0x02 ? 0x80 : 0); - interleaved[i + 7] += (byte)((p[i / 8] & 0x01) == 0x01 ? 0x80 : 0); - - interleaved[i] += (byte)((q[i / 8] & 0x80) == 0x80 ? 0x40 : 0); - interleaved[i + 1] += (byte)((q[i / 8] & 0x40) == 0x40 ? 0x40 : 0); - interleaved[i + 2] += (byte)((q[i / 8] & 0x20) == 0x20 ? 0x40 : 0); - interleaved[i + 3] += (byte)((q[i / 8] & 0x10) == 0x10 ? 0x40 : 0); - interleaved[i + 4] += (byte)((q[i / 8] & 0x08) == 0x08 ? 0x40 : 0); - interleaved[i + 5] += (byte)((q[i / 8] & 0x04) == 0x04 ? 0x40 : 0); - interleaved[i + 6] += (byte)((q[i / 8] & 0x02) == 0x02 ? 0x40 : 0); - interleaved[i + 7] += (byte)((q[i / 8] & 0x01) == 0x01 ? 0x40 : 0); - - interleaved[i] += (byte)((r[i / 8] & 0x80) == 0x80 ? 0x20 : 0); - interleaved[i + 1] += (byte)((r[i / 8] & 0x40) == 0x40 ? 0x20 : 0); - interleaved[i + 2] += (byte)((r[i / 8] & 0x20) == 0x20 ? 0x20 : 0); - interleaved[i + 3] += (byte)((r[i / 8] & 0x10) == 0x10 ? 0x20 : 0); - interleaved[i + 4] += (byte)((r[i / 8] & 0x08) == 0x08 ? 0x20 : 0); - interleaved[i + 5] += (byte)((r[i / 8] & 0x04) == 0x04 ? 0x20 : 0); - interleaved[i + 6] += (byte)((r[i / 8] & 0x02) == 0x02 ? 0x20 : 0); - interleaved[i + 7] += (byte)((r[i / 8] & 0x01) == 0x01 ? 0x20 : 0); - - interleaved[i] += (byte)((s[i / 8] & 0x80) == 0x80 ? 0x10 : 0); - interleaved[i + 1] += (byte)((s[i / 8] & 0x40) == 0x40 ? 0x10 : 0); - interleaved[i + 2] += (byte)((s[i / 8] & 0x20) == 0x20 ? 0x10 : 0); - interleaved[i + 3] += (byte)((s[i / 8] & 0x10) == 0x10 ? 0x10 : 0); - interleaved[i + 4] += (byte)((s[i / 8] & 0x08) == 0x08 ? 0x10 : 0); - interleaved[i + 5] += (byte)((s[i / 8] & 0x04) == 0x04 ? 0x10 : 0); - interleaved[i + 6] += (byte)((s[i / 8] & 0x02) == 0x02 ? 0x10 : 0); - interleaved[i + 7] += (byte)((s[i / 8] & 0x01) == 0x01 ? 0x10 : 0); - - interleaved[i] += (byte)((t[i / 8] & 0x80) == 0x80 ? 0x08 : 0); - interleaved[i + 1] += (byte)((t[i / 8] & 0x40) == 0x40 ? 0x08 : 0); - interleaved[i + 2] += (byte)((t[i / 8] & 0x20) == 0x20 ? 0x08 : 0); - interleaved[i + 3] += (byte)((t[i / 8] & 0x10) == 0x10 ? 0x08 : 0); - interleaved[i + 4] += (byte)((t[i / 8] & 0x08) == 0x08 ? 0x08 : 0); - interleaved[i + 5] += (byte)((t[i / 8] & 0x04) == 0x04 ? 0x08 : 0); - interleaved[i + 6] += (byte)((t[i / 8] & 0x02) == 0x02 ? 0x08 : 0); - interleaved[i + 7] += (byte)((t[i / 8] & 0x01) == 0x01 ? 0x08 : 0); - - interleaved[i] += (byte)((u[i / 8] & 0x80) == 0x80 ? 0x04 : 0); - interleaved[i + 1] += (byte)((u[i / 8] & 0x40) == 0x40 ? 0x04 : 0); - interleaved[i + 2] += (byte)((u[i / 8] & 0x20) == 0x20 ? 0x04 : 0); - interleaved[i + 3] += (byte)((u[i / 8] & 0x10) == 0x10 ? 0x04 : 0); - interleaved[i + 4] += (byte)((u[i / 8] & 0x08) == 0x08 ? 0x04 : 0); - interleaved[i + 5] += (byte)((u[i / 8] & 0x04) == 0x04 ? 0x04 : 0); - interleaved[i + 6] += (byte)((u[i / 8] & 0x02) == 0x02 ? 0x04 : 0); - interleaved[i + 7] += (byte)((u[i / 8] & 0x01) == 0x01 ? 0x04 : 0); - - interleaved[i] += (byte)((v[i / 8] & 0x80) == 0x80 ? 0x02 : 0); - interleaved[i + 1] += (byte)((v[i / 8] & 0x40) == 0x40 ? 0x02 : 0); - interleaved[i + 2] += (byte)((v[i / 8] & 0x20) == 0x20 ? 0x02 : 0); - interleaved[i + 3] += (byte)((v[i / 8] & 0x10) == 0x10 ? 0x02 : 0); - interleaved[i + 4] += (byte)((v[i / 8] & 0x08) == 0x08 ? 0x02 : 0); - interleaved[i + 5] += (byte)((v[i / 8] & 0x04) == 0x04 ? 0x02 : 0); - interleaved[i + 6] += (byte)((v[i / 8] & 0x02) == 0x02 ? 0x02 : 0); - interleaved[i + 7] += (byte)((v[i / 8] & 0x01) == 0x01 ? 0x02 : 0); - - interleaved[i] += (byte)((w[i / 8] & 0x80) == 0x80 ? 0x01 : 0); - interleaved[i + 1] += (byte)((w[i / 8] & 0x40) == 0x40 ? 0x01 : 0); - interleaved[i + 2] += (byte)((w[i / 8] & 0x20) == 0x20 ? 0x01 : 0); - interleaved[i + 3] += (byte)((w[i / 8] & 0x10) == 0x10 ? 0x01 : 0); - interleaved[i + 4] += (byte)((w[i / 8] & 0x08) == 0x08 ? 0x01 : 0); - interleaved[i + 5] += (byte)((w[i / 8] & 0x04) == 0x04 ? 0x01 : 0); - interleaved[i + 6] += (byte)((w[i / 8] & 0x02) == 0x02 ? 0x01 : 0); - interleaved[i + 7] += (byte)((w[i / 8] & 0x01) == 0x01 ? 0x01 : 0); - } - - end = DateTime.UtcNow; - TimeSpan interleave = end - start; - - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to de-sequentialize subchannel.", - desequentialize.TotalMilliseconds); - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to interleave subchannel.", - interleave.TotalMilliseconds); - DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to untransform subchannel.", - interleave.TotalMilliseconds + desequentialize.TotalMilliseconds); - - return interleaved; - } - - /// <summary>List of known compression types</summary> - enum CompressionType : ushort - { - /// <summary>Not compressed</summary> - None = 0, - /// <summary>LZMA</summary> - Lzma = 1, - /// <summary>FLAC</summary> - Flac = 2, - /// <summary>LZMA in Claunia Subchannel Transform processed data</summary> - LzmaClauniaSubchannelTransform = 3 - } - - /// <summary>List of known data types</summary> - enum DataType : ushort - { - /// <summary>No data</summary> - NoData = 0, - /// <summary>User data</summary> - UserData = 1, - /// <summary>CompactDisc partial Table of Contents</summary> - CompactDiscPartialToc = 2, - /// <summary>CompactDisc session information</summary> - CompactDiscSessionInfo = 3, - /// <summary>CompactDisc Table of Contents</summary> - CompactDiscToc = 4, - /// <summary>CompactDisc Power Management Area</summary> - CompactDiscPma = 5, - /// <summary>CompactDisc Absolute Time In Pregroove</summary> - CompactDiscAtip = 6, - /// <summary>CompactDisc Lead-in's CD-Text</summary> - CompactDiscLeadInCdText = 7, - /// <summary>DVD Physical Format Information</summary> - DvdPfi = 8, - /// <summary>DVD Lead-in's Copyright Management Information</summary> - DvdLeadInCmi = 9, - /// <summary>DVD Disc Key</summary> - DvdDiscKey = 10, - /// <summary>DVD Burst Cutting Area</summary> - DvdBca = 11, - /// <summary>DVD DMI</summary> - DvdDmi = 12, - /// <summary>DVD Media Identifier</summary> - DvdMediaIdentifier = 13, - /// <summary>DVD Media Key Block</summary> - DvdMediaKeyBlock = 14, - /// <summary>DVD-RAM Disc Definition Structure</summary> - DvdRamDds = 15, - /// <summary>DVD-RAM Medium Status</summary> - DvdRamMediumStatus = 16, - /// <summary>DVD-RAM Spare Area Information</summary> - DvdRamSpareArea = 17, - /// <summary>DVD-R RMD</summary> - DvdRRmd = 18, - /// <summary>DVD-R Pre-recorded Information</summary> - DvdRPrerecordedInfo = 19, - /// <summary>DVD-R Media Identifier</summary> - DvdRMediaIdentifier = 20, - /// <summary>DVD-R Physical Format Information</summary> - DvdRPfi = 21, - /// <summary>DVD ADress In Pregroove</summary> - DvdAdip = 22, - /// <summary>HD DVD Copy Protection Information</summary> - HdDvdCpi = 23, - /// <summary>HD DVD Medium Status</summary> - HdDvdMediumStatus = 24, - /// <summary>DVD DL Layer Capacity</summary> - DvdDlLayerCapacity = 25, - /// <summary>DVD DL Middle Zone Address</summary> - DvdDlMiddleZoneAddress = 26, - /// <summary>DVD DL Jump Interval Size</summary> - DvdDlJumpIntervalSize = 27, - /// <summary>DVD DL Manual Layer Jump LBA</summary> - DvdDlManualLayerJumpLba = 28, - /// <summary>Bluray Disc Information</summary> - BlurayDi = 29, - /// <summary>Bluray Burst Cutting Area</summary> - BlurayBca = 30, - /// <summary>Bluray Disc Definition Structure</summary> - BlurayDds = 31, - /// <summary>Bluray Cartridge Status</summary> - BlurayCartridgeStatus = 32, - /// <summary>Bluray Spare Area Information</summary> - BluraySpareArea = 33, - /// <summary>AACS Volume Identifier</summary> - AacsVolumeIdentifier = 34, - /// <summary>AACS Serial Number</summary> - AacsSerialNumber = 35, - /// <summary>AACS Media Identifier</summary> - AacsMediaIdentifier = 36, - /// <summary>AACS Media Key Block</summary> - AacsMediaKeyBlock = 37, - /// <summary>AACS Data Keys</summary> - AacsDataKeys = 38, - /// <summary>AACS LBA Extents</summary> - AacsLbaExtents = 39, - /// <summary>CPRM Media Key Block</summary> - CprmMediaKeyBlock = 40, - /// <summary>Recognized Layers</summary> - HybridRecognizedLayers = 41, - /// <summary>MMC Write Protection</summary> - ScsiMmcWriteProtection = 42, - /// <summary>MMC Disc Information</summary> - ScsiMmcDiscInformation = 43, - /// <summary>MMC Track Resources Information</summary> - ScsiMmcTrackResourcesInformation = 44, - /// <summary>MMC POW Resources Information</summary> - ScsiMmcPowResourcesInformation = 45, - /// <summary>SCSI INQUIRY RESPONSE</summary> - ScsiInquiry = 46, - /// <summary>SCSI MODE PAGE 2Ah</summary> - ScsiModePage2A = 47, - /// <summary>ATA IDENTIFY response</summary> - AtaIdentify = 48, - /// <summary>ATAPI IDENTIFY response</summary> - AtapiIdentify = 49, - /// <summary>PCMCIA CIS</summary> - PcmciaCis = 50, - /// <summary>SecureDigital CID</summary> - SecureDigitalCid = 51, - /// <summary>SecureDigital CSD</summary> - SecureDigitalCsd = 52, - /// <summary>SecureDigital SCR</summary> - SecureDigitalScr = 53, - /// <summary>SecureDigital OCR</summary> - SecureDigitalOcr = 54, - /// <summary>MultiMediaCard CID</summary> - MultiMediaCardCid = 55, - /// <summary>MultiMediaCard CSD</summary> - MultiMediaCardCsd = 56, - /// <summary>MultiMediaCard OCR</summary> - MultiMediaCardOcr = 57, - /// <summary>MultiMediaCard Extended CSD</summary> - MultiMediaCardExtendedCsd = 58, - /// <summary>Xbox Security Sector</summary> - XboxSecuritySector = 59, - /// <summary>Floppy Lead-out</summary> - FloppyLeadOut = 60, - /// <summary>Dvd Disc Control Block</summary> - DvdDiscControlBlock = 61, - /// <summary>CompactDisc Lead-in</summary> - CompactDiscLeadIn = 62, - /// <summary>CompactDisc Lead-out</summary> - CompactDiscLeadOut = 63, - /// <summary>SCSI MODE SENSE (6) response</summary> - ScsiModeSense6 = 64, - /// <summary>SCSI MODE SENSE (10) response</summary> - ScsiModeSense10 = 65, - /// <summary>USB descriptors</summary> - UsbDescriptors = 66, - /// <summary>Xbox DMI</summary> - XboxDmi = 67, - /// <summary>Xbox Physical Format Information</summary> - XboxPfi = 68, - /// <summary>CompactDisc sector prefix (sync, header</summary> - CdSectorPrefix = 69, - /// <summary>CompactDisc sector suffix (edc, ecc p, ecc q)</summary> - CdSectorSuffix = 70, - /// <summary>CompactDisc subchannel</summary> - CdSectorSubchannel = 71, - /// <summary>Apple Profile (20 byte) tag</summary> - AppleProfileTag = 72, - /// <summary>Apple Sony (12 byte) tag</summary> - AppleSonyTag = 73, - /// <summary>Priam Data Tower (24 byte) tag</summary> - PriamDataTowerTag = 74, - /// <summary>CompactDisc Media Catalogue Number (as in Lead-in), 13 bytes, ASCII</summary> - CompactDiscMediaCatalogueNumber = 75 - } - - /// <summary>List of known blocks types</summary> - enum BlockType : uint - { - /// <summary>Block containing data</summary> - DataBlock = 0x4B4C4244, - /// <summary>Block containing a deduplication table</summary> - DeDuplicationTable = 0X2A544444, - /// <summary>Block containing the index</summary> - Index = 0X58444E49, - /// <summary>Block containing logical geometry</summary> - GeometryBlock = 0x4D4F4547, - /// <summary>Block containing metadata</summary> - MetadataBlock = 0x4154454D, - /// <summary>Block containing optical disc tracks</summary> - TracksBlock = 0x534B5254, - /// <summary>Block containing CICM XML metadata</summary> - CicmBlock = 0x4D434943, - /// <summary>Block containing contents checksums</summary> - ChecksumBlock = 0x4D534B43, - /// <summary>TODO: Block containing data position measurements</summary> - DataPositionMeasurementBlock = 0x2A4D5044, - /// <summary>TODO: Block containing a snapshot index</summary> - SnapshotBlock = 0x50414E53, - /// <summary>TODO: Block containing how to locate the parent image</summary> - ParentBlock = 0x50524E54, - /// <summary>Block containing an array of hardware used to create the image</summary> - DumpHardwareBlock = 0x2A504D44, - /// <summary>TODO: Block containing list of files for a tape image</summary> - TapeFileBlock = 0x454C4654 - } - - /// <summary>Header, at start of file</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] - struct DicHeader - { - /// <summary>Header identifier, <see cref="DIC_MAGIC" /></summary> - public ulong identifier; - /// <summary>UTF-16LE name of the application that created the image</summary> - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] - public string application; - /// <summary>Image format major version. A new major version means a possibly incompatible change of format</summary> - public byte imageMajorVersion; - /// <summary>Image format minor version. A new minor version indicates a compatible change of format</summary> - public byte imageMinorVersion; - /// <summary>Major version of the application that created the image</summary> - public byte applicationMajorVersion; - /// <summary>Minor version of the application that created the image</summary> - public byte applicationMinorVersion; - /// <summary>Type of media contained on image</summary> - public MediaType mediaType; - /// <summary>Offset to index</summary> - public ulong indexOffset; - /// <summary>Windows filetime (100 nanoseconds since 1601/01/01 00:00:00 UTC) of image creation time</summary> - public long creationTime; - /// <summary>Windows filetime (100 nanoseconds since 1601/01/01 00:00:00 UTC) of image last written time</summary> - public long lastWrittenTime; - } - - /// <summary>Header for a deduplication table. Table follows it</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DdtHeader - { - /// <summary>Identifier, <see cref="BlockType.DeDuplicationTable" /></summary> - public BlockType identifier; - /// <summary>Type of data pointed by this DDT</summary> - public DataType type; - /// <summary>Compression algorithm used to compress the DDT</summary> - public CompressionType compression; - /// <summary>Each entry is ((byte offset in file) << shift) + (sector offset in block)</summary> - public byte shift; - /// <summary>How many entries are in the table</summary> - public ulong entries; - /// <summary>Compressed length for the DDT</summary> - public ulong cmpLength; - /// <summary>Uncompressed length for the DDT</summary> - public ulong length; - /// <summary>CRC64-ECMA of the compressed DDT</summary> - public ulong cmpCrc64; - /// <summary>CRC64-ECMA of the uncompressed DDT</summary> - public ulong crc64; - } - - /// <summary>Header for the index, followed by entries</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct IndexHeader - { - /// <summary>Identifier, <see cref="BlockType.Index" /></summary> - public BlockType identifier; - /// <summary>How many entries follow this header</summary> - public ushort entries; - /// <summary>CRC64-ECMA of the index</summary> - public ulong crc64; - } - - /// <summary>Index entry</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct IndexEntry - { - /// <summary>Type of item pointed by this entry</summary> - public BlockType blockType; - /// <summary>Type of data contained by the block pointed by this entry</summary> - public DataType dataType; - /// <summary>Offset in file where item is stored</summary> - public ulong offset; - } - - /// <summary>Block header, precedes block data</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct BlockHeader - { - /// <summary>Identifier, <see cref="BlockType.DataBlock" /></summary> - public BlockType identifier; - /// <summary>Type of data contained by this block</summary> - public DataType type; - /// <summary>Compression algorithm used to compress the block</summary> - public CompressionType compression; - /// <summary>Size in bytes of each sector contained in this block</summary> - public uint sectorSize; - /// <summary>Compressed length for the block</summary> - public uint cmpLength; - /// <summary>Uncompressed length for the block</summary> - public uint length; - /// <summary>CRC64-ECMA of the compressed block</summary> - public ulong cmpCrc64; - /// <summary>CRC64-ECMA of the uncompressed block</summary> - public ulong crc64; - } - - /// <summary>Geometry block, contains physical geometry information</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct GeometryBlock - { - /// <summary>Identifier, <see cref="BlockType.GeometryBlock" /></summary> - public BlockType identifier; - public uint cylinders; - public uint heads; - public uint sectorsPerTrack; - } - - /// <summary>Metadata block, contains metadata</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct MetadataBlock - { - /// <summary>Identifier, <see cref="BlockType.MetadataBlock" /></summary> - public BlockType identifier; - /// <summary>Size in bytes of this whole metadata block</summary> - public uint blockSize; - /// <summary>Sequence of media set this media belongs to</summary> - public int mediaSequence; - /// <summary>Total number of media on the media set this media belongs to</summary> - public int lastMediaSequence; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint creatorOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint creatorLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint commentsOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint commentsLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint mediaTitleOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint mediaTitleLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint mediaManufacturerOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint mediaManufacturerLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint mediaModelOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint mediaModelLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint mediaSerialNumberOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint mediaSerialNumberLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint mediaBarcodeOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint mediaBarcodeLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint mediaPartNumberOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint mediaPartNumberLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint driveManufacturerOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint driveManufacturerLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint driveModelOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint driveModelLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint driveSerialNumberOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint driveSerialNumberLength; - /// <summary>Offset to start of creator string from start of this block</summary> - public uint driveFirmwareRevisionOffset; - /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> - public uint driveFirmwareRevisionLength; - } - - /// <summary>Contains list of optical disc tracks</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct TracksHeader - { - /// <summary>Identifier, <see cref="BlockType.TracksBlock" /></summary> - public BlockType identifier; - /// <summary>How many entries follow this header</summary> - public ushort entries; - /// <summary>CRC64-ECMA of the block</summary> - public ulong crc64; - } - - /// <summary>Optical disc track</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] - struct TrackEntry - { - /// <summary>Track sequence</summary> - public byte sequence; - /// <summary>Track type</summary> - public TrackType type; - /// <summary>Track starting LBA</summary> - public long start; - /// <summary>Track last LBA</summary> - public long end; - /// <summary>Track pregap in sectors</summary> - public long pregap; - /// <summary>Track session</summary> - public byte session; - /// <summary>Track's ISRC in ASCII</summary> - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] - public string isrc; - /// <summary>Track flags</summary> - public byte flags; - } - - /// <summary>Geometry block, contains physical geometry information</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CicmMetadataBlock - { - /// <summary>Identifier, <see cref="BlockType.CicmBlock" /></summary> - public BlockType identifier; - public uint length; - } - - /// <summary>Dump hardware block, contains a list of hardware used to dump the media on this image</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DumpHardwareHeader - { - /// <summary>Identifier, <see cref="BlockType.DumpHardwareBlock" /></summary> - public BlockType identifier; - /// <summary>How many entries follow this header</summary> - public ushort entries; - /// <summary>Size of the whole block, not including this header, in bytes</summary> - public uint length; - /// <summary>CRC64-ECMA of the block</summary> - public ulong crc64; - } - - /// <summary>Dump hardware entry, contains length of strings that follow, in the same order as the length, this structure</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DumpHardwareEntry - { - /// <summary>Length of UTF-8 manufacturer string</summary> - public uint manufacturerLength; - /// <summary>Length of UTF-8 model string</summary> - public uint modelLength; - /// <summary>Length of UTF-8 revision string</summary> - public uint revisionLength; - /// <summary>Length of UTF-8 firmware version string</summary> - public uint firmwareLength; - /// <summary>Length of UTF-8 serial string</summary> - public uint serialLength; - /// <summary>Length of UTF-8 software name string</summary> - public uint softwareNameLength; - /// <summary>Length of UTF-8 software version string</summary> - public uint softwareVersionLength; - /// <summary>Length of UTF-8 software operating system string</summary> - public uint softwareOperatingSystemLength; - /// <summary>How many extents are after the strings</summary> - public uint extents; - } - - /// <summary>Dump hardware extent, first and last sector dumped, both inclusive</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DumpHardwareExtent - { - public ulong start; - public ulong end; - } - - /// <summary> - /// Checksum block, contains a checksum of all user data sectors (except for optical discs that is 2352 bytes raw - /// sector if available - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChecksumHeader - { - /// <summary>Identifier, <see cref="BlockType.ChecksumBlock" /></summary> - public BlockType identifier; - /// <summary>Length in bytes of the block</summary> - public uint length; - /// <summary>How many checksums follow</summary> - public byte entries; - } - - enum ChecksumAlgorithm : byte - { - Invalid = 0, - Md5 = 1, - Sha1 = 2, - Sha256 = 3, - SpamSum = 4 - } - - /// <summary>Checksum entry, followed by checksum data itself</summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChecksumEntry - { - /// <summary>Checksum algorithm</summary> - public ChecksumAlgorithm type; - /// <summary>Length in bytes of checksum that follows this structure</summary> - public uint length; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/ClauniaSubchannelTransform.cs b/DiscImageChef.DiscImages/DiscImageChef/ClauniaSubchannelTransform.cs new file mode 100644 index 000000000..4ab6968ea --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/ClauniaSubchannelTransform.cs @@ -0,0 +1,296 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : ClauniaSubchannelTransform.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains the Claunia Subchannel Transform algorithm. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + static byte[] ClauniaSubchannelTransform(byte[] interleaved) + { + if(interleaved == null) return null; + + int[] p = new int[interleaved.Length / 8]; + int[] q = new int[interleaved.Length / 8]; + int[] r = new int[interleaved.Length / 8]; + int[] s = new int[interleaved.Length / 8]; + int[] t = new int[interleaved.Length / 8]; + int[] u = new int[interleaved.Length / 8]; + int[] v = new int[interleaved.Length / 8]; + int[] w = new int[interleaved.Length / 8]; + + DateTime start = DateTime.UtcNow; + for(int i = 0; i < interleaved.Length; i += 8) + { + p[i / 8] = interleaved[i] & 0x80; + p[i / 8] += (interleaved[i + 1] & 0x80) >> 1; + p[i / 8] += (interleaved[i + 2] & 0x80) >> 2; + p[i / 8] += (interleaved[i + 3] & 0x80) >> 3; + p[i / 8] += (interleaved[i + 4] & 0x80) >> 4; + p[i / 8] += (interleaved[i + 5] & 0x80) >> 5; + p[i / 8] += (interleaved[i + 6] & 0x80) >> 6; + p[i / 8] += (interleaved[i + 7] & 0x80) >> 7; + + q[i / 8] = (interleaved[i] & 0x40) << 1; + q[i / 8] += interleaved[i + 1] & 0x40; + q[i / 8] += (interleaved[i + 2] & 0x40) >> 1; + q[i / 8] += (interleaved[i + 3] & 0x40) >> 2; + q[i / 8] += (interleaved[i + 4] & 0x40) >> 3; + q[i / 8] += (interleaved[i + 5] & 0x40) >> 4; + q[i / 8] += (interleaved[i + 6] & 0x40) >> 5; + q[i / 8] += (interleaved[i + 7] & 0x40) >> 6; + + r[i / 8] = (interleaved[i] & 0x20) << 2; + r[i / 8] += (interleaved[i + 1] & 0x20) << 1; + r[i / 8] += interleaved[i + 2] & 0x20; + r[i / 8] += (interleaved[i + 3] & 0x20) >> 1; + r[i / 8] += (interleaved[i + 4] & 0x20) >> 2; + r[i / 8] += (interleaved[i + 5] & 0x20) >> 3; + r[i / 8] += (interleaved[i + 6] & 0x20) >> 4; + r[i / 8] += (interleaved[i + 7] & 0x20) >> 5; + + s[i / 8] = (interleaved[i] & 0x10) << 3; + s[i / 8] += (interleaved[i + 1] & 0x10) << 2; + s[i / 8] += (interleaved[i + 2] & 0x10) << 1; + s[i / 8] += interleaved[i + 3] & 0x10; + s[i / 8] += (interleaved[i + 4] & 0x10) >> 1; + s[i / 8] += (interleaved[i + 5] & 0x10) >> 2; + s[i / 8] += (interleaved[i + 6] & 0x10) >> 3; + s[i / 8] += (interleaved[i + 7] & 0x10) >> 4; + + t[i / 8] = (interleaved[i] & 0x08) << 4; + t[i / 8] += (interleaved[i + 1] & 0x08) << 3; + t[i / 8] += (interleaved[i + 2] & 0x08) << 2; + t[i / 8] += (interleaved[i + 3] & 0x08) << 1; + t[i / 8] += interleaved[i + 4] & 0x08; + t[i / 8] += (interleaved[i + 5] & 0x08) >> 1; + t[i / 8] += (interleaved[i + 6] & 0x08) >> 2; + t[i / 8] += (interleaved[i + 7] & 0x08) >> 3; + + u[i / 8] = (interleaved[i] & 0x04) << 5; + u[i / 8] += (interleaved[i + 1] & 0x04) << 4; + u[i / 8] += (interleaved[i + 2] & 0x04) << 3; + u[i / 8] += (interleaved[i + 3] & 0x04) << 2; + u[i / 8] += (interleaved[i + 4] & 0x04) << 1; + u[i / 8] += interleaved[i + 5] & 0x04; + u[i / 8] += (interleaved[i + 6] & 0x04) >> 1; + u[i / 8] += (interleaved[i + 7] & 0x04) >> 2; + + v[i / 8] = (interleaved[i] & 0x02) << 6; + v[i / 8] += (interleaved[i + 1] & 0x02) << 5; + v[i / 8] += (interleaved[i + 2] & 0x02) << 4; + v[i / 8] += (interleaved[i + 3] & 0x02) << 3; + v[i / 8] += (interleaved[i + 4] & 0x02) << 2; + v[i / 8] += (interleaved[i + 5] & 0x02) << 1; + v[i / 8] += interleaved[i + 6] & 0x02; + v[i / 8] += (interleaved[i + 7] & 0x02) >> 1; + + w[i / 8] = (interleaved[i] & 0x01) << 7; + w[i / 8] += (interleaved[i + 1] & 0x01) << 6; + w[i / 8] += (interleaved[i + 2] & 0x01) << 5; + w[i / 8] += (interleaved[i + 3] & 0x01) << 4; + w[i / 8] += (interleaved[i + 4] & 0x01) << 3; + w[i / 8] += (interleaved[i + 5] & 0x01) << 2; + w[i / 8] += (interleaved[i + 6] & 0x01) << 1; + w[i / 8] += interleaved[i + 7] & 0x01; + } + + DateTime end = DateTime.UtcNow; + TimeSpan deinterleave = end - start; + + byte[] sequential = new byte[interleaved.Length]; + start = DateTime.UtcNow; + + int qStart = p.Length * 1; + int rStart = p.Length * 2; + int sStart = p.Length * 3; + int tStart = p.Length * 4; + int uStart = p.Length * 5; + int vStart = p.Length * 6; + int wStart = p.Length * 7; + + for(int i = 0; i < p.Length; i++) + { + sequential[i] = (byte)p[i]; + sequential[qStart + i] = (byte)q[i]; + sequential[rStart + i] = (byte)r[i]; + sequential[sStart + i] = (byte)s[i]; + sequential[tStart + i] = (byte)t[i]; + sequential[uStart + i] = (byte)u[i]; + sequential[vStart + i] = (byte)v[i]; + sequential[wStart + i] = (byte)w[i]; + } + + end = DateTime.UtcNow; + TimeSpan sequentialize = end - start; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to deinterleave subchannel.", + deinterleave.TotalMilliseconds); + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to sequentialize subchannel.", + sequentialize.TotalMilliseconds); + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to transform subchannel.", + deinterleave.TotalMilliseconds + sequentialize.TotalMilliseconds); + + return sequential; + } + + static byte[] ClauniaSubchannelUntransform(byte[] sequential) + { + if(sequential == null) return null; + + int[] p = new int[sequential.Length / 8]; + int[] q = new int[sequential.Length / 8]; + int[] r = new int[sequential.Length / 8]; + int[] s = new int[sequential.Length / 8]; + int[] t = new int[sequential.Length / 8]; + int[] u = new int[sequential.Length / 8]; + int[] v = new int[sequential.Length / 8]; + int[] w = new int[sequential.Length / 8]; + + int qStart = p.Length * 1; + int rStart = p.Length * 2; + int sStart = p.Length * 3; + int tStart = p.Length * 4; + int uStart = p.Length * 5; + int vStart = p.Length * 6; + int wStart = p.Length * 7; + + DateTime start = DateTime.UtcNow; + + for(int i = 0; i < p.Length; i++) + { + p[i] = sequential[i]; + q[i] = sequential[qStart + i]; + r[i] = sequential[rStart + i]; + s[i] = sequential[sStart + i]; + t[i] = sequential[tStart + i]; + u[i] = sequential[uStart + i]; + v[i] = sequential[vStart + i]; + w[i] = sequential[wStart + i]; + } + + DateTime end = DateTime.UtcNow; + TimeSpan desequentialize = end - start; + + byte[] interleaved = new byte[sequential.Length]; + start = DateTime.UtcNow; + for(int i = 0; i < interleaved.Length; i += 8) + { + interleaved[i] = (byte)((p[i / 8] & 0x80) == 0x80 ? 0x80 : 0); + interleaved[i + 1] += (byte)((p[i / 8] & 0x40) == 0x40 ? 0x80 : 0); + interleaved[i + 2] += (byte)((p[i / 8] & 0x20) == 0x20 ? 0x80 : 0); + interleaved[i + 3] += (byte)((p[i / 8] & 0x10) == 0x10 ? 0x80 : 0); + interleaved[i + 4] += (byte)((p[i / 8] & 0x08) == 0x08 ? 0x80 : 0); + interleaved[i + 5] += (byte)((p[i / 8] & 0x04) == 0x04 ? 0x80 : 0); + interleaved[i + 6] += (byte)((p[i / 8] & 0x02) == 0x02 ? 0x80 : 0); + interleaved[i + 7] += (byte)((p[i / 8] & 0x01) == 0x01 ? 0x80 : 0); + + interleaved[i] += (byte)((q[i / 8] & 0x80) == 0x80 ? 0x40 : 0); + interleaved[i + 1] += (byte)((q[i / 8] & 0x40) == 0x40 ? 0x40 : 0); + interleaved[i + 2] += (byte)((q[i / 8] & 0x20) == 0x20 ? 0x40 : 0); + interleaved[i + 3] += (byte)((q[i / 8] & 0x10) == 0x10 ? 0x40 : 0); + interleaved[i + 4] += (byte)((q[i / 8] & 0x08) == 0x08 ? 0x40 : 0); + interleaved[i + 5] += (byte)((q[i / 8] & 0x04) == 0x04 ? 0x40 : 0); + interleaved[i + 6] += (byte)((q[i / 8] & 0x02) == 0x02 ? 0x40 : 0); + interleaved[i + 7] += (byte)((q[i / 8] & 0x01) == 0x01 ? 0x40 : 0); + + interleaved[i] += (byte)((r[i / 8] & 0x80) == 0x80 ? 0x20 : 0); + interleaved[i + 1] += (byte)((r[i / 8] & 0x40) == 0x40 ? 0x20 : 0); + interleaved[i + 2] += (byte)((r[i / 8] & 0x20) == 0x20 ? 0x20 : 0); + interleaved[i + 3] += (byte)((r[i / 8] & 0x10) == 0x10 ? 0x20 : 0); + interleaved[i + 4] += (byte)((r[i / 8] & 0x08) == 0x08 ? 0x20 : 0); + interleaved[i + 5] += (byte)((r[i / 8] & 0x04) == 0x04 ? 0x20 : 0); + interleaved[i + 6] += (byte)((r[i / 8] & 0x02) == 0x02 ? 0x20 : 0); + interleaved[i + 7] += (byte)((r[i / 8] & 0x01) == 0x01 ? 0x20 : 0); + + interleaved[i] += (byte)((s[i / 8] & 0x80) == 0x80 ? 0x10 : 0); + interleaved[i + 1] += (byte)((s[i / 8] & 0x40) == 0x40 ? 0x10 : 0); + interleaved[i + 2] += (byte)((s[i / 8] & 0x20) == 0x20 ? 0x10 : 0); + interleaved[i + 3] += (byte)((s[i / 8] & 0x10) == 0x10 ? 0x10 : 0); + interleaved[i + 4] += (byte)((s[i / 8] & 0x08) == 0x08 ? 0x10 : 0); + interleaved[i + 5] += (byte)((s[i / 8] & 0x04) == 0x04 ? 0x10 : 0); + interleaved[i + 6] += (byte)((s[i / 8] & 0x02) == 0x02 ? 0x10 : 0); + interleaved[i + 7] += (byte)((s[i / 8] & 0x01) == 0x01 ? 0x10 : 0); + + interleaved[i] += (byte)((t[i / 8] & 0x80) == 0x80 ? 0x08 : 0); + interleaved[i + 1] += (byte)((t[i / 8] & 0x40) == 0x40 ? 0x08 : 0); + interleaved[i + 2] += (byte)((t[i / 8] & 0x20) == 0x20 ? 0x08 : 0); + interleaved[i + 3] += (byte)((t[i / 8] & 0x10) == 0x10 ? 0x08 : 0); + interleaved[i + 4] += (byte)((t[i / 8] & 0x08) == 0x08 ? 0x08 : 0); + interleaved[i + 5] += (byte)((t[i / 8] & 0x04) == 0x04 ? 0x08 : 0); + interleaved[i + 6] += (byte)((t[i / 8] & 0x02) == 0x02 ? 0x08 : 0); + interleaved[i + 7] += (byte)((t[i / 8] & 0x01) == 0x01 ? 0x08 : 0); + + interleaved[i] += (byte)((u[i / 8] & 0x80) == 0x80 ? 0x04 : 0); + interleaved[i + 1] += (byte)((u[i / 8] & 0x40) == 0x40 ? 0x04 : 0); + interleaved[i + 2] += (byte)((u[i / 8] & 0x20) == 0x20 ? 0x04 : 0); + interleaved[i + 3] += (byte)((u[i / 8] & 0x10) == 0x10 ? 0x04 : 0); + interleaved[i + 4] += (byte)((u[i / 8] & 0x08) == 0x08 ? 0x04 : 0); + interleaved[i + 5] += (byte)((u[i / 8] & 0x04) == 0x04 ? 0x04 : 0); + interleaved[i + 6] += (byte)((u[i / 8] & 0x02) == 0x02 ? 0x04 : 0); + interleaved[i + 7] += (byte)((u[i / 8] & 0x01) == 0x01 ? 0x04 : 0); + + interleaved[i] += (byte)((v[i / 8] & 0x80) == 0x80 ? 0x02 : 0); + interleaved[i + 1] += (byte)((v[i / 8] & 0x40) == 0x40 ? 0x02 : 0); + interleaved[i + 2] += (byte)((v[i / 8] & 0x20) == 0x20 ? 0x02 : 0); + interleaved[i + 3] += (byte)((v[i / 8] & 0x10) == 0x10 ? 0x02 : 0); + interleaved[i + 4] += (byte)((v[i / 8] & 0x08) == 0x08 ? 0x02 : 0); + interleaved[i + 5] += (byte)((v[i / 8] & 0x04) == 0x04 ? 0x02 : 0); + interleaved[i + 6] += (byte)((v[i / 8] & 0x02) == 0x02 ? 0x02 : 0); + interleaved[i + 7] += (byte)((v[i / 8] & 0x01) == 0x01 ? 0x02 : 0); + + interleaved[i] += (byte)((w[i / 8] & 0x80) == 0x80 ? 0x01 : 0); + interleaved[i + 1] += (byte)((w[i / 8] & 0x40) == 0x40 ? 0x01 : 0); + interleaved[i + 2] += (byte)((w[i / 8] & 0x20) == 0x20 ? 0x01 : 0); + interleaved[i + 3] += (byte)((w[i / 8] & 0x10) == 0x10 ? 0x01 : 0); + interleaved[i + 4] += (byte)((w[i / 8] & 0x08) == 0x08 ? 0x01 : 0); + interleaved[i + 5] += (byte)((w[i / 8] & 0x04) == 0x04 ? 0x01 : 0); + interleaved[i + 6] += (byte)((w[i / 8] & 0x02) == 0x02 ? 0x01 : 0); + interleaved[i + 7] += (byte)((w[i / 8] & 0x01) == 0x01 ? 0x01 : 0); + } + + end = DateTime.UtcNow; + TimeSpan interleave = end - start; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to de-sequentialize subchannel.", + desequentialize.TotalMilliseconds); + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to interleave subchannel.", + interleave.TotalMilliseconds); + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0}ms to untransform subchannel.", + interleave.TotalMilliseconds + desequentialize.TotalMilliseconds); + + return interleaved; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Constants.cs b/DiscImageChef.DiscImages/DiscImageChef/Constants.cs new file mode 100644 index 000000000..3dbd102fc --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Constants.cs @@ -0,0 +1,60 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + /// <summary>Magic identidier = "DICMFMT".</summary> + const ulong DIC_MAGIC = 0x544D52464D434944; + /// <summary> + /// Image format version. A change in this number indicates an incompatible change to the format that + /// prevents older implementations from reading it correctly, if at all. + /// </summary> + const byte DICF_VERSION = 1; + /// <summary>Maximum read cache size, 256MiB.</summary> + const uint MAX_CACHE_SIZE = 256 * 1024 * 1024; + /// <summary>Size in bytes of LZMA properties.</summary> + const int LZMA_PROPERTIES_LENGTH = 5; + /// <summary>Maximum number of entries for the DDT cache.</summary> + const int MAX_DDT_ENTRY_CACHE = 16000000; + /// <summary>How many samples are contained in a RedBook sector.</summary> + const int SAMPLES_PER_SECTOR = 588; + /// <summary>Maximum number of samples for a FLAC block. Bigger than 4608 gives no benefit.</summary> + const int MAX_FLAKE_BLOCK = 4608; + /// <summary> + /// Minimum number of samples for a FLAC block. <see cref="CUETools.Codecs.FLAKE" /> does not support it to be + /// smaller than 256. + /// </summary> + const int MIN_FLAKE_BLOCK = 256; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/DiscImageChef.cs b/DiscImageChef.DiscImages/DiscImageChef/DiscImageChef.cs new file mode 100644 index 000000000..02166d338 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/DiscImageChef.cs @@ -0,0 +1,188 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : DiscImageChef.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +/* + The idea of the format is being able to easily store, retrieve, and access any data that can be read from media. + + At the start of a file there's a header that contains a format version, application creator name, and a pointer to + the index. + + The index points to one or several DeDuplication Tables, or media tag blocks. + + A deduplication table is a table of offsets to blocks and sectors inside blocks. Each entry equals to an LBA and points + to a byte offset in the file shift left to the number of sectors contained in a block, plus the number of sector inside + the block. + Each block must contain sectors of equal size, but that size can be different between blocks. + The deduplication table should be stored decompressed if its size is too big to be stored on-memory. This is chosen at + creation time but it is a good idea to set the limit to 256MiB (this allows for a device of 33 million sectors, + 17Gb at 512 bps, 68Gb at 2048 bps and 137Gb at 4096 bps). + + Sector tags that are simply too small to be deduplicated are contained in a single block pointed by the index (e.g. + Apple GCR sector tags). + + Optical disks contain a track block that describes the tracks. + TODO: Streaming tapes contain a file block that describes the files and an optional partition block that describes the tape + partitions. + + There are also blocks for image metadata, contents metadata and dump hardware information. + + A differencing image will have all the metadata and deduplication tables, but the entries in these ones will be set to + 0 if the block is stored in the parent image. TODO: This is not yet implemented. + + Also because the file becomes useless without the index and deduplication table, each can be stored twice. In case of + the index it should just be searched for. In case of deduplication tables, both copies should be indexed. + + Finally, writing new data to an existing image is just Copy-On-Write. Create a new block with the modified data, change + the pointer in the corresponding deduplication table. + + P.S.: Data Position Measurement is doable, as soon as I know how to do it. + P.S.2: Support for floppy image containg bitslices and/or fluxes will be added soon. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using CUETools.Codecs.FLAKE; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; +using SharpCompress.Compressors.LZMA; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef : IWritableImage + { + bool alreadyWrittenZero; + /// <summary>Cache of uncompressed blocks.</summary> + Dictionary<ulong, byte[]> blockCache; + /// <summary>Cache of block headers.</summary> + Dictionary<ulong, BlockHeader> blockHeaderCache; + /// <summary>Stream used for writing blocks.</summary> + MemoryStream blockStream; + /// <summary>Provides checksum for deduplication of sectors.</summary> + SHA256 checksumProvider; + /// <summary>Provides CRC64.</summary> + Crc64Context crc64; + /// <summary>Header of the currently writing block.</summary> + BlockHeader currentBlockHeader; + /// <summary>Sector offset of writing position in currently writing block.</summary> + uint currentBlockOffset; + /// <summary>Current size in bytes of the block cache</summary> + uint currentCacheSize; + /// <summary>Cache of DDT entries.</summary> + Dictionary<ulong, ulong> ddtEntryCache; + MemoryStream decompressedStream; + bool deduplicate; + /// <summary>On-memory deduplication table indexed by checksum.</summary> + Dictionary<string, ulong> deduplicationTable; + /// <summary><see cref="CUETools.Codecs.FLAKE" /> writer.</summary> + FlakeWriter flakeWriter; + /// <summary><see cref="CUETools.Codecs.FLAKE" /> settings.</summary> + FlakeWriterSettings flakeWriterSettings; + /// <summary>Block with logical geometry.</summary> + GeometryBlock geometryBlock; + /// <summary>Image header.</summary> + DicHeader header; + /// <summary>Image information.</summary> + ImageInfo imageInfo; + /// <summary>Image data stream.</summary> + Stream imageStream; + /// <summary>Index.</summary> + List<IndexEntry> index; + /// <summary>If set to <c>true</c>, the DDT entries are in-memory.</summary> + bool inMemoryDdt; + ulong lastWrittenBlock; + /// <summary>LZMA stream.</summary> + LzmaStream lzmaBlockStream; + /// <summary>LZMA properties.</summary> + LzmaEncoderProperties lzmaEncoderProperties; + Md5Context md5Provider; + /// <summary>Cache of media tags.</summary> + Dictionary<MediaTagType, byte[]> mediaTags; + bool nocompress; + /// <summary>If DDT is on-disk, this is the image stream offset at which it starts.</summary> + long outMemoryDdtPosition; + bool rewinded; + /// <summary>Cache for data that prefixes the user data on a sector (e.g. sync).</summary> + byte[] sectorPrefix; + /// <summary>Cache for data that goes side by side with user data (e.g. CompactDisc subchannel).</summary> + byte[] sectorSubchannel; + /// <summary>Cache for data that suffixes the user data on a sector (e.g. edc, ecc).</summary> + byte[] sectorSuffix; + Sha1Context sha1Provider; + Sha256Context sha256Provider; + /// <summary>Shift for calculating number of sectors in a block.</summary> + byte shift; + SpamSumContext spamsumProvider; + /// <summary>Cache for bytes to write/rad on-disk.</summary> + byte[] structureBytes; + /// <summary>Cache for pointer for marshaling structures.</summary> + IntPtr structurePointer; + /// <summary>Cache of CompactDisc track's flags</summary> + Dictionary<byte, byte> trackFlags; + /// <summary>Cache of CompactDisc track's ISRC</summary> + Dictionary<byte, string> trackIsrcs; + /// <summary>In-memory deduplication table</summary> + ulong[] userDataDdt; + bool writingLong; + ulong writtenSectors; + + public DiscImageChef() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = "DiscImageChef", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Enums.cs b/DiscImageChef.DiscImages/DiscImageChef/Enums.cs new file mode 100644 index 000000000..5df7ff313 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Enums.cs @@ -0,0 +1,247 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + /// <summary>List of known compression types</summary> + enum CompressionType : ushort + { + /// <summary>Not compressed</summary> + None = 0, + /// <summary>LZMA</summary> + Lzma = 1, + /// <summary>FLAC</summary> + Flac = 2, + /// <summary>LZMA in Claunia Subchannel Transform processed data</summary> + LzmaClauniaSubchannelTransform = 3 + } + + /// <summary>List of known data types</summary> + enum DataType : ushort + { + /// <summary>No data</summary> + NoData = 0, + /// <summary>User data</summary> + UserData = 1, + /// <summary>CompactDisc partial Table of Contents</summary> + CompactDiscPartialToc = 2, + /// <summary>CompactDisc session information</summary> + CompactDiscSessionInfo = 3, + /// <summary>CompactDisc Table of Contents</summary> + CompactDiscToc = 4, + /// <summary>CompactDisc Power Management Area</summary> + CompactDiscPma = 5, + /// <summary>CompactDisc Absolute Time In Pregroove</summary> + CompactDiscAtip = 6, + /// <summary>CompactDisc Lead-in's CD-Text</summary> + CompactDiscLeadInCdText = 7, + /// <summary>DVD Physical Format Information</summary> + DvdPfi = 8, + /// <summary>DVD Lead-in's Copyright Management Information</summary> + DvdLeadInCmi = 9, + /// <summary>DVD Disc Key</summary> + DvdDiscKey = 10, + /// <summary>DVD Burst Cutting Area</summary> + DvdBca = 11, + /// <summary>DVD DMI</summary> + DvdDmi = 12, + /// <summary>DVD Media Identifier</summary> + DvdMediaIdentifier = 13, + /// <summary>DVD Media Key Block</summary> + DvdMediaKeyBlock = 14, + /// <summary>DVD-RAM Disc Definition Structure</summary> + DvdRamDds = 15, + /// <summary>DVD-RAM Medium Status</summary> + DvdRamMediumStatus = 16, + /// <summary>DVD-RAM Spare Area Information</summary> + DvdRamSpareArea = 17, + /// <summary>DVD-R RMD</summary> + DvdRRmd = 18, + /// <summary>DVD-R Pre-recorded Information</summary> + DvdRPrerecordedInfo = 19, + /// <summary>DVD-R Media Identifier</summary> + DvdRMediaIdentifier = 20, + /// <summary>DVD-R Physical Format Information</summary> + DvdRPfi = 21, + /// <summary>DVD ADress In Pregroove</summary> + DvdAdip = 22, + /// <summary>HD DVD Copy Protection Information</summary> + HdDvdCpi = 23, + /// <summary>HD DVD Medium Status</summary> + HdDvdMediumStatus = 24, + /// <summary>DVD DL Layer Capacity</summary> + DvdDlLayerCapacity = 25, + /// <summary>DVD DL Middle Zone Address</summary> + DvdDlMiddleZoneAddress = 26, + /// <summary>DVD DL Jump Interval Size</summary> + DvdDlJumpIntervalSize = 27, + /// <summary>DVD DL Manual Layer Jump LBA</summary> + DvdDlManualLayerJumpLba = 28, + /// <summary>Bluray Disc Information</summary> + BlurayDi = 29, + /// <summary>Bluray Burst Cutting Area</summary> + BlurayBca = 30, + /// <summary>Bluray Disc Definition Structure</summary> + BlurayDds = 31, + /// <summary>Bluray Cartridge Status</summary> + BlurayCartridgeStatus = 32, + /// <summary>Bluray Spare Area Information</summary> + BluraySpareArea = 33, + /// <summary>AACS Volume Identifier</summary> + AacsVolumeIdentifier = 34, + /// <summary>AACS Serial Number</summary> + AacsSerialNumber = 35, + /// <summary>AACS Media Identifier</summary> + AacsMediaIdentifier = 36, + /// <summary>AACS Media Key Block</summary> + AacsMediaKeyBlock = 37, + /// <summary>AACS Data Keys</summary> + AacsDataKeys = 38, + /// <summary>AACS LBA Extents</summary> + AacsLbaExtents = 39, + /// <summary>CPRM Media Key Block</summary> + CprmMediaKeyBlock = 40, + /// <summary>Recognized Layers</summary> + HybridRecognizedLayers = 41, + /// <summary>MMC Write Protection</summary> + ScsiMmcWriteProtection = 42, + /// <summary>MMC Disc Information</summary> + ScsiMmcDiscInformation = 43, + /// <summary>MMC Track Resources Information</summary> + ScsiMmcTrackResourcesInformation = 44, + /// <summary>MMC POW Resources Information</summary> + ScsiMmcPowResourcesInformation = 45, + /// <summary>SCSI INQUIRY RESPONSE</summary> + ScsiInquiry = 46, + /// <summary>SCSI MODE PAGE 2Ah</summary> + ScsiModePage2A = 47, + /// <summary>ATA IDENTIFY response</summary> + AtaIdentify = 48, + /// <summary>ATAPI IDENTIFY response</summary> + AtapiIdentify = 49, + /// <summary>PCMCIA CIS</summary> + PcmciaCis = 50, + /// <summary>SecureDigital CID</summary> + SecureDigitalCid = 51, + /// <summary>SecureDigital CSD</summary> + SecureDigitalCsd = 52, + /// <summary>SecureDigital SCR</summary> + SecureDigitalScr = 53, + /// <summary>SecureDigital OCR</summary> + SecureDigitalOcr = 54, + /// <summary>MultiMediaCard CID</summary> + MultiMediaCardCid = 55, + /// <summary>MultiMediaCard CSD</summary> + MultiMediaCardCsd = 56, + /// <summary>MultiMediaCard OCR</summary> + MultiMediaCardOcr = 57, + /// <summary>MultiMediaCard Extended CSD</summary> + MultiMediaCardExtendedCsd = 58, + /// <summary>Xbox Security Sector</summary> + XboxSecuritySector = 59, + /// <summary>Floppy Lead-out</summary> + FloppyLeadOut = 60, + /// <summary>Dvd Disc Control Block</summary> + DvdDiscControlBlock = 61, + /// <summary>CompactDisc Lead-in</summary> + CompactDiscLeadIn = 62, + /// <summary>CompactDisc Lead-out</summary> + CompactDiscLeadOut = 63, + /// <summary>SCSI MODE SENSE (6) response</summary> + ScsiModeSense6 = 64, + /// <summary>SCSI MODE SENSE (10) response</summary> + ScsiModeSense10 = 65, + /// <summary>USB descriptors</summary> + UsbDescriptors = 66, + /// <summary>Xbox DMI</summary> + XboxDmi = 67, + /// <summary>Xbox Physical Format Information</summary> + XboxPfi = 68, + /// <summary>CompactDisc sector prefix (sync, header</summary> + CdSectorPrefix = 69, + /// <summary>CompactDisc sector suffix (edc, ecc p, ecc q)</summary> + CdSectorSuffix = 70, + /// <summary>CompactDisc subchannel</summary> + CdSectorSubchannel = 71, + /// <summary>Apple Profile (20 byte) tag</summary> + AppleProfileTag = 72, + /// <summary>Apple Sony (12 byte) tag</summary> + AppleSonyTag = 73, + /// <summary>Priam Data Tower (24 byte) tag</summary> + PriamDataTowerTag = 74, + /// <summary>CompactDisc Media Catalogue Number (as in Lead-in), 13 bytes, ASCII</summary> + CompactDiscMediaCatalogueNumber = 75 + } + + /// <summary>List of known blocks types</summary> + enum BlockType : uint + { + /// <summary>Block containing data</summary> + DataBlock = 0x4B4C4244, + /// <summary>Block containing a deduplication table</summary> + DeDuplicationTable = 0X2A544444, + /// <summary>Block containing the index</summary> + Index = 0X58444E49, + /// <summary>Block containing logical geometry</summary> + GeometryBlock = 0x4D4F4547, + /// <summary>Block containing metadata</summary> + MetadataBlock = 0x4154454D, + /// <summary>Block containing optical disc tracks</summary> + TracksBlock = 0x534B5254, + /// <summary>Block containing CICM XML metadata</summary> + CicmBlock = 0x4D434943, + /// <summary>Block containing contents checksums</summary> + ChecksumBlock = 0x4D534B43, + /// <summary>TODO: Block containing data position measurements</summary> + DataPositionMeasurementBlock = 0x2A4D5044, + /// <summary>TODO: Block containing a snapshot index</summary> + SnapshotBlock = 0x50414E53, + /// <summary>TODO: Block containing how to locate the parent image</summary> + ParentBlock = 0x50524E54, + /// <summary>Block containing an array of hardware used to create the image</summary> + DumpHardwareBlock = 0x2A504D44, + /// <summary>TODO: Block containing list of files for a tape image</summary> + TapeFileBlock = 0x454C4654 + } + + enum ChecksumAlgorithm : byte + { + Invalid = 0, + Md5 = 1, + Sha1 = 2, + Sha256 = 3, + SpamSum = 4 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Helpers.cs b/DiscImageChef.DiscImages/DiscImageChef/Helpers.cs new file mode 100644 index 000000000..107c3ce9d --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Helpers.cs @@ -0,0 +1,407 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.Decoders.ATA; +using DiscImageChef.Decoders.SCSI; +using DiscImageChef.Decoders.SecureDigital; +using VendorString = DiscImageChef.Decoders.SecureDigital.VendorString; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + /// <summary> + /// Checks for media tags that may contain metadata and sets it up if not already set + /// </summary> + void SetMetadataFromTags() + { + // Search for SecureDigital CID + if(mediaTags.TryGetValue(MediaTagType.SD_CID, out byte[] sdCid)) + { + CID decoded = Decoders.SecureDigital.Decoders.DecodeCID(sdCid); + if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) + imageInfo.DriveManufacturer = VendorString.Prettify(decoded.Manufacturer); + if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) imageInfo.DriveModel = decoded.ProductName; + if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) + imageInfo.DriveFirmwareRevision = + $"{(decoded.ProductRevision & 0xF0) >> 4:X2}.{decoded.ProductRevision & 0x0F:X2}"; + if(string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) + imageInfo.DriveSerialNumber = $"{decoded.ProductSerialNumber}"; + } + + // Search for MultiMediaCard CID + if(mediaTags.TryGetValue(MediaTagType.MMC_CID, out byte[] mmcCid)) + { + Decoders.MMC.CID decoded = Decoders.MMC.Decoders.DecodeCID(mmcCid); + if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) + imageInfo.DriveManufacturer = Decoders.MMC.VendorString.Prettify(decoded.Manufacturer); + if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) imageInfo.DriveModel = decoded.ProductName; + if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) + imageInfo.DriveFirmwareRevision = + $"{(decoded.ProductRevision & 0xF0) >> 4:X2}.{decoded.ProductRevision & 0x0F:X2}"; + if(string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) + imageInfo.DriveSerialNumber = $"{decoded.ProductSerialNumber}"; + } + + // Search for SCSI INQUIRY + if(mediaTags.TryGetValue(MediaTagType.SCSI_INQUIRY, out byte[] scsiInquiry)) + { + Inquiry.SCSIInquiry? nullableInquiry = Inquiry.Decode(scsiInquiry); + + if(nullableInquiry.HasValue) + { + Inquiry.SCSIInquiry inquiry = nullableInquiry.Value; + if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) + imageInfo.DriveManufacturer = StringHandlers.CToString(inquiry.VendorIdentification)?.Trim(); + if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) + imageInfo.DriveModel = StringHandlers.CToString(inquiry.ProductIdentification)?.Trim(); + if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) + imageInfo.DriveFirmwareRevision = + StringHandlers.CToString(inquiry.ProductRevisionLevel)?.Trim(); + } + } + + // Search for ATA or ATAPI IDENTIFY + if(!mediaTags.TryGetValue(MediaTagType.ATA_IDENTIFY, out byte[] ataIdentify) && + !mediaTags.TryGetValue(MediaTagType.ATAPI_IDENTIFY, out ataIdentify)) return; + + Identify.IdentifyDevice? nullableIdentify = Decoders.ATA.Identify.Decode(ataIdentify); + + if(!nullableIdentify.HasValue) return; + + Identify.IdentifyDevice identify = nullableIdentify.Value; + + string[] separated = identify.Model.Split(' '); + + if(separated.Length == 1) + if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) + imageInfo.DriveModel = separated[0]; + else + { + if(string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) + imageInfo.DriveManufacturer = separated[0]; + if(string.IsNullOrWhiteSpace(imageInfo.DriveModel)) + imageInfo.DriveModel = separated[separated.Length - 1]; + } + + if(string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) + imageInfo.DriveFirmwareRevision = identify.FirmwareRevision; + if(string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) + imageInfo.DriveSerialNumber = identify.SerialNumber; + } + + // Get the CICM XML media type from DIC media type + static XmlMediaType GetXmlMediaType(MediaType type) + { + switch(type) + { + case MediaType.CD: + case MediaType.CDDA: + case MediaType.CDG: + case MediaType.CDEG: + case MediaType.CDI: + case MediaType.CDROM: + case MediaType.CDROMXA: + case MediaType.CDPLUS: + case MediaType.CDMO: + case MediaType.CDR: + case MediaType.CDRW: + case MediaType.CDMRW: + case MediaType.VCD: + case MediaType.SVCD: + case MediaType.PCD: + case MediaType.SACD: + case MediaType.DDCD: + case MediaType.DDCDR: + case MediaType.DDCDRW: + case MediaType.DTSCD: + case MediaType.CDMIDI: + case MediaType.CDV: + case MediaType.DVDROM: + case MediaType.DVDR: + case MediaType.DVDRW: + case MediaType.DVDPR: + case MediaType.DVDPRW: + case MediaType.DVDPRWDL: + case MediaType.DVDRDL: + case MediaType.DVDPRDL: + case MediaType.DVDRAM: + case MediaType.DVDRWDL: + case MediaType.DVDDownload: + case MediaType.HDDVDROM: + case MediaType.HDDVDRAM: + case MediaType.HDDVDR: + case MediaType.HDDVDRW: + case MediaType.HDDVDRDL: + case MediaType.HDDVDRWDL: + case MediaType.BDROM: + case MediaType.BDR: + case MediaType.BDRE: + case MediaType.BDRXL: + case MediaType.BDREXL: + case MediaType.EVD: + case MediaType.FVD: + case MediaType.HVD: + case MediaType.CBHD: + case MediaType.HDVMD: + case MediaType.VCDHD: + case MediaType.SVOD: + case MediaType.FDDVD: + case MediaType.LD: + case MediaType.LDROM: + case MediaType.LDROM2: + case MediaType.LVROM: + case MediaType.MegaLD: + case MediaType.PS1CD: + case MediaType.PS2CD: + case MediaType.PS2DVD: + case MediaType.PS3DVD: + case MediaType.PS3BD: + case MediaType.PS4BD: + case MediaType.UMD: + case MediaType.XGD: + case MediaType.XGD2: + case MediaType.XGD3: + case MediaType.XGD4: + case MediaType.MEGACD: + case MediaType.SATURNCD: + case MediaType.GDROM: + case MediaType.GDR: + case MediaType.SuperCDROM2: + case MediaType.JaguarCD: + case MediaType.ThreeDO: + case MediaType.PCFX: + case MediaType.NeoGeoCD: + case MediaType.GOD: + case MediaType.WOD: + case MediaType.WUOD: return XmlMediaType.OpticalDisc; + default: return XmlMediaType.BlockMedia; + } + } + + // Gets a DDT entry + ulong GetDdtEntry(ulong sectorAddress) + { + if(inMemoryDdt) return userDataDdt[sectorAddress]; + + if(ddtEntryCache.TryGetValue(sectorAddress, out ulong entry)) return entry; + + long oldPosition = imageStream.Position; + imageStream.Position = outMemoryDdtPosition + Marshal.SizeOf(typeof(DdtHeader)); + imageStream.Position += (long)(sectorAddress * sizeof(ulong)); + byte[] temp = new byte[sizeof(ulong)]; + imageStream.Read(temp, 0, sizeof(ulong)); + imageStream.Position = oldPosition; + entry = BitConverter.ToUInt64(temp, 0); + + if(ddtEntryCache.Count >= MAX_DDT_ENTRY_CACHE) ddtEntryCache.Clear(); + + ddtEntryCache.Add(sectorAddress, entry); + return entry; + } + + // Sets a DDT entry + void SetDdtEntry(ulong sectorAddress, ulong pointer) + { + if(inMemoryDdt) + { + userDataDdt[sectorAddress] = pointer; + return; + } + + long oldPosition = imageStream.Position; + imageStream.Position = outMemoryDdtPosition + Marshal.SizeOf(typeof(DdtHeader)); + imageStream.Position += (long)(sectorAddress * sizeof(ulong)); + imageStream.Write(BitConverter.GetBytes(pointer), 0, sizeof(ulong)); + imageStream.Position = oldPosition; + } + + // Converts between image data type and dic media tag type + static MediaTagType GetMediaTagTypeForDataType(DataType type) + { + switch(type) + { + case DataType.CompactDiscPartialToc: return MediaTagType.CD_TOC; + case DataType.CompactDiscSessionInfo: return MediaTagType.CD_SessionInfo; + case DataType.CompactDiscToc: return MediaTagType.CD_FullTOC; + case DataType.CompactDiscPma: return MediaTagType.CD_PMA; + case DataType.CompactDiscAtip: return MediaTagType.CD_ATIP; + case DataType.CompactDiscLeadInCdText: return MediaTagType.CD_TEXT; + case DataType.DvdPfi: return MediaTagType.DVD_PFI; + case DataType.DvdLeadInCmi: return MediaTagType.DVD_CMI; + case DataType.DvdDiscKey: return MediaTagType.DVD_DiscKey; + case DataType.DvdBca: return MediaTagType.DVD_BCA; + case DataType.DvdDmi: return MediaTagType.DVD_DMI; + case DataType.DvdMediaIdentifier: return MediaTagType.DVD_MediaIdentifier; + case DataType.DvdMediaKeyBlock: return MediaTagType.DVD_MKB; + case DataType.DvdRamDds: return MediaTagType.DVDRAM_DDS; + case DataType.DvdRamMediumStatus: return MediaTagType.DVDRAM_MediumStatus; + case DataType.DvdRamSpareArea: return MediaTagType.DVDRAM_SpareArea; + case DataType.DvdRRmd: return MediaTagType.DVDR_RMD; + case DataType.DvdRPrerecordedInfo: return MediaTagType.DVDR_PreRecordedInfo; + case DataType.DvdRMediaIdentifier: return MediaTagType.DVDR_MediaIdentifier; + case DataType.DvdRPfi: return MediaTagType.DVDR_PFI; + case DataType.DvdAdip: return MediaTagType.DVD_ADIP; + case DataType.HdDvdCpi: return MediaTagType.HDDVD_CPI; + case DataType.HdDvdMediumStatus: return MediaTagType.HDDVD_MediumStatus; + case DataType.DvdDlLayerCapacity: return MediaTagType.DVDDL_LayerCapacity; + case DataType.DvdDlMiddleZoneAddress: return MediaTagType.DVDDL_MiddleZoneAddress; + case DataType.DvdDlJumpIntervalSize: return MediaTagType.DVDDL_JumpIntervalSize; + case DataType.DvdDlManualLayerJumpLba: return MediaTagType.DVDDL_ManualLayerJumpLBA; + case DataType.BlurayDi: return MediaTagType.BD_DI; + case DataType.BlurayBca: return MediaTagType.BD_BCA; + case DataType.BlurayDds: return MediaTagType.BD_DDS; + case DataType.BlurayCartridgeStatus: return MediaTagType.BD_CartridgeStatus; + case DataType.BluraySpareArea: return MediaTagType.BD_SpareArea; + case DataType.AacsVolumeIdentifier: return MediaTagType.AACS_VolumeIdentifier; + case DataType.AacsSerialNumber: return MediaTagType.AACS_SerialNumber; + case DataType.AacsMediaIdentifier: return MediaTagType.AACS_MediaIdentifier; + case DataType.AacsMediaKeyBlock: return MediaTagType.AACS_MKB; + case DataType.AacsDataKeys: return MediaTagType.AACS_DataKeys; + case DataType.AacsLbaExtents: return MediaTagType.AACS_LBAExtents; + case DataType.CprmMediaKeyBlock: return MediaTagType.AACS_CPRM_MKB; + case DataType.HybridRecognizedLayers: return MediaTagType.Hybrid_RecognizedLayers; + case DataType.ScsiMmcWriteProtection: return MediaTagType.MMC_WriteProtection; + case DataType.ScsiMmcDiscInformation: return MediaTagType.MMC_DiscInformation; + case DataType.ScsiMmcTrackResourcesInformation: return MediaTagType.MMC_TrackResourcesInformation; + case DataType.ScsiMmcPowResourcesInformation: return MediaTagType.MMC_POWResourcesInformation; + case DataType.ScsiInquiry: return MediaTagType.SCSI_INQUIRY; + case DataType.ScsiModePage2A: return MediaTagType.SCSI_MODEPAGE_2A; + case DataType.AtaIdentify: return MediaTagType.ATA_IDENTIFY; + case DataType.AtapiIdentify: return MediaTagType.ATAPI_IDENTIFY; + case DataType.PcmciaCis: return MediaTagType.PCMCIA_CIS; + case DataType.SecureDigitalCid: return MediaTagType.SD_CID; + case DataType.SecureDigitalCsd: return MediaTagType.SD_CSD; + case DataType.SecureDigitalScr: return MediaTagType.SD_SCR; + case DataType.SecureDigitalOcr: return MediaTagType.SD_OCR; + case DataType.MultiMediaCardCid: return MediaTagType.MMC_CID; + case DataType.MultiMediaCardCsd: return MediaTagType.MMC_CSD; + case DataType.MultiMediaCardOcr: return MediaTagType.MMC_OCR; + case DataType.MultiMediaCardExtendedCsd: return MediaTagType.MMC_ExtendedCSD; + case DataType.XboxSecuritySector: return MediaTagType.Xbox_SecuritySector; + case DataType.FloppyLeadOut: return MediaTagType.Floppy_LeadOut; + case DataType.DvdDiscControlBlock: return MediaTagType.DCB; + case DataType.CompactDiscLeadIn: return MediaTagType.CD_LeadIn; + case DataType.CompactDiscLeadOut: return MediaTagType.CD_LeadOut; + case DataType.ScsiModeSense6: return MediaTagType.SCSI_MODESENSE_6; + case DataType.ScsiModeSense10: return MediaTagType.SCSI_MODESENSE_10; + case DataType.UsbDescriptors: return MediaTagType.USB_Descriptors; + case DataType.XboxDmi: return MediaTagType.Xbox_DMI; + case DataType.XboxPfi: return MediaTagType.Xbox_PFI; + case DataType.CompactDiscMediaCatalogueNumber: return MediaTagType.CD_MCN; + default: throw new ArgumentOutOfRangeException(); + } + } + + // Converts between dic media tag type and image data type + static DataType GetDataTypeForMediaTag(MediaTagType tag) + { + switch(tag) + { + case MediaTagType.CD_TOC: return DataType.CompactDiscPartialToc; + case MediaTagType.CD_SessionInfo: return DataType.CompactDiscSessionInfo; + case MediaTagType.CD_FullTOC: return DataType.CompactDiscToc; + case MediaTagType.CD_PMA: return DataType.CompactDiscPma; + case MediaTagType.CD_ATIP: return DataType.CompactDiscAtip; + case MediaTagType.CD_TEXT: return DataType.CompactDiscLeadInCdText; + case MediaTagType.DVD_PFI: return DataType.DvdPfi; + case MediaTagType.DVD_CMI: return DataType.DvdLeadInCmi; + case MediaTagType.DVD_DiscKey: return DataType.DvdDiscKey; + case MediaTagType.DVD_BCA: return DataType.DvdBca; + case MediaTagType.DVD_DMI: return DataType.DvdDmi; + case MediaTagType.DVD_MediaIdentifier: return DataType.DvdMediaIdentifier; + case MediaTagType.DVD_MKB: return DataType.DvdMediaKeyBlock; + case MediaTagType.DVDRAM_DDS: return DataType.DvdRamDds; + case MediaTagType.DVDRAM_MediumStatus: return DataType.DvdRamMediumStatus; + case MediaTagType.DVDRAM_SpareArea: return DataType.DvdRamSpareArea; + case MediaTagType.DVDR_RMD: return DataType.DvdRRmd; + case MediaTagType.DVDR_PreRecordedInfo: return DataType.DvdRPrerecordedInfo; + case MediaTagType.DVDR_MediaIdentifier: return DataType.DvdRMediaIdentifier; + case MediaTagType.DVDR_PFI: return DataType.DvdRPfi; + case MediaTagType.DVD_ADIP: return DataType.DvdAdip; + case MediaTagType.HDDVD_CPI: return DataType.HdDvdCpi; + case MediaTagType.HDDVD_MediumStatus: return DataType.HdDvdMediumStatus; + case MediaTagType.DVDDL_LayerCapacity: return DataType.DvdDlLayerCapacity; + case MediaTagType.DVDDL_MiddleZoneAddress: return DataType.DvdDlMiddleZoneAddress; + case MediaTagType.DVDDL_JumpIntervalSize: return DataType.DvdDlJumpIntervalSize; + case MediaTagType.DVDDL_ManualLayerJumpLBA: return DataType.DvdDlManualLayerJumpLba; + case MediaTagType.BD_DI: return DataType.BlurayDi; + case MediaTagType.BD_BCA: return DataType.BlurayBca; + case MediaTagType.BD_DDS: return DataType.BlurayDds; + case MediaTagType.BD_CartridgeStatus: return DataType.BlurayCartridgeStatus; + case MediaTagType.BD_SpareArea: return DataType.BluraySpareArea; + case MediaTagType.AACS_VolumeIdentifier: return DataType.AacsVolumeIdentifier; + case MediaTagType.AACS_SerialNumber: return DataType.AacsSerialNumber; + case MediaTagType.AACS_MediaIdentifier: return DataType.AacsMediaIdentifier; + case MediaTagType.AACS_MKB: return DataType.AacsMediaKeyBlock; + case MediaTagType.AACS_DataKeys: return DataType.AacsDataKeys; + case MediaTagType.AACS_LBAExtents: return DataType.AacsLbaExtents; + case MediaTagType.AACS_CPRM_MKB: return DataType.CprmMediaKeyBlock; + case MediaTagType.Hybrid_RecognizedLayers: return DataType.HybridRecognizedLayers; + case MediaTagType.MMC_WriteProtection: return DataType.ScsiMmcWriteProtection; + case MediaTagType.MMC_DiscInformation: return DataType.ScsiMmcDiscInformation; + case MediaTagType.MMC_TrackResourcesInformation: return DataType.ScsiMmcTrackResourcesInformation; + case MediaTagType.MMC_POWResourcesInformation: return DataType.ScsiMmcPowResourcesInformation; + case MediaTagType.SCSI_INQUIRY: return DataType.ScsiInquiry; + case MediaTagType.SCSI_MODEPAGE_2A: return DataType.ScsiModePage2A; + case MediaTagType.ATA_IDENTIFY: return DataType.AtaIdentify; + case MediaTagType.ATAPI_IDENTIFY: return DataType.AtapiIdentify; + case MediaTagType.PCMCIA_CIS: return DataType.PcmciaCis; + case MediaTagType.SD_CID: return DataType.SecureDigitalCid; + case MediaTagType.SD_CSD: return DataType.SecureDigitalCsd; + case MediaTagType.SD_SCR: return DataType.SecureDigitalScr; + case MediaTagType.SD_OCR: return DataType.SecureDigitalOcr; + case MediaTagType.MMC_CID: return DataType.MultiMediaCardCid; + case MediaTagType.MMC_CSD: return DataType.MultiMediaCardCsd; + case MediaTagType.MMC_OCR: return DataType.MultiMediaCardOcr; + case MediaTagType.MMC_ExtendedCSD: return DataType.MultiMediaCardExtendedCsd; + case MediaTagType.Xbox_SecuritySector: return DataType.XboxSecuritySector; + case MediaTagType.Floppy_LeadOut: return DataType.FloppyLeadOut; + case MediaTagType.DCB: return DataType.DvdDiscControlBlock; + case MediaTagType.CD_LeadIn: return DataType.CompactDiscLeadIn; + case MediaTagType.CD_LeadOut: return DataType.CompactDiscLeadOut; + case MediaTagType.SCSI_MODESENSE_6: return DataType.ScsiModeSense6; + case MediaTagType.SCSI_MODESENSE_10: return DataType.ScsiModeSense10; + case MediaTagType.USB_Descriptors: return DataType.UsbDescriptors; + case MediaTagType.Xbox_DMI: return DataType.XboxDmi; + case MediaTagType.Xbox_PFI: return DataType.XboxPfi; + case MediaTagType.CD_MCN: return DataType.CompactDiscMediaCatalogueNumber; + default: + throw new ArgumentOutOfRangeException(nameof(tag), tag, null); + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Identify.cs b/DiscImageChef.DiscImages/DiscImageChef/Identify.cs new file mode 100644 index 000000000..73bb9fced --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Identify.cs @@ -0,0 +1,59 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + public bool Identify(IFilter imageFilter) + { + imageStream = imageFilter.GetDataForkStream(); + imageStream.Seek(0, SeekOrigin.Begin); + + if(imageStream.Length < Marshal.SizeOf(header)) return false; + + header = new DicHeader(); + structureBytes = new byte[Marshal.SizeOf(header)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); + header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); + Marshal.FreeHGlobal(structurePointer); + + return header.identifier == DIC_MAGIC && header.imageMajorVersion <= DICF_VERSION; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Properties.cs b/DiscImageChef.DiscImages/DiscImageChef/Properties.cs new file mode 100644 index 000000000..df268a8b0 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Properties.cs @@ -0,0 +1,79 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + public ImageInfo Info => imageInfo; + public string Name => "DiscImageChef format"; + public Guid Id => new Guid("49360069-1784-4A2F-B723-0C844D610B0A"); + public string Format => "DiscImageChef"; + public List<Partition> Partitions { get; private set; } + public List<Track> Tracks { get; private set; } + public List<Session> Sessions { get; private set; } + public List<DumpHardwareType> DumpHardware { get; private set; } + public CICMMetadataType CicmMetadata { get; private set; } + public IEnumerable<MediaTagType> SupportedMediaTags => + Enum.GetValues(typeof(MediaTagType)).Cast<MediaTagType>(); + public IEnumerable<SectorTagType> SupportedSectorTags => + Enum.GetValues(typeof(SectorTagType)).Cast<SectorTagType>(); + public IEnumerable<MediaType> SupportedMediaTypes => Enum.GetValues(typeof(MediaType)).Cast<MediaType>(); + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new[] + { + ("sectors_per_block", typeof(uint), + "How many sectors to store per block (will be rounded to next power of two)"), + ("dictionary", typeof(uint), "Size, in bytes, of the LZMA dictionary"), + ("max_ddt_size", typeof(uint), + "Maximum size, in mebibytes, for in-memory DDT. If image needs a bigger one, it will be on-disk"), + ("md5", typeof(bool), "Calculate and store MD5 of image's user data"), + ("sha1", typeof(bool), "Calculate and store SHA1 of image's user data"), + ("sha256", typeof(bool), "Calculate and store SHA256 of image's user data"), + ("spamsum", typeof(bool), "Calculate and store SpamSum of image's user data"), + ("deduplicate", typeof(bool), + "Store only unique sectors. This consumes more memory and is slower, but it's enabled by default"), + ("nocompress", typeof(bool), "Don't compress user data blocks. Other blocks will still be compressed") + }; + public IEnumerable<string> KnownExtensions => new[] {".dicf"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Read.cs b/DiscImageChef.DiscImages/DiscImageChef/Read.cs new file mode 100644 index 000000000..2278e8045 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Read.cs @@ -0,0 +1,1423 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using CUETools.Codecs; +using CUETools.Codecs.FLAKE; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Console; +using Schemas; +using SharpCompress.Compressors.LZMA; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + public bool Open(IFilter imageFilter) + { + imageStream = imageFilter.GetDataForkStream(); + imageStream.Seek(0, SeekOrigin.Begin); + + if(imageStream.Length < Marshal.SizeOf(header)) return false; + + header = new DicHeader(); + structureBytes = new byte[Marshal.SizeOf(header)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); + header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); + Marshal.FreeHGlobal(structurePointer); + + if(header.imageMajorVersion > DICF_VERSION) + throw new FeatureUnsupportedImageException($"Image version {header.imageMajorVersion} not recognized."); + + imageInfo.Application = header.application; + imageInfo.ApplicationVersion = $"{header.applicationMajorVersion}.{header.applicationMinorVersion}"; + imageInfo.Version = $"{header.imageMajorVersion}.{header.imageMinorVersion}"; + imageInfo.MediaType = header.mediaType; + + // Read the index header + imageStream.Position = (long)header.indexOffset; + IndexHeader idxHeader = new IndexHeader(); + structureBytes = new byte[Marshal.SizeOf(idxHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(idxHeader)); + idxHeader = (IndexHeader)Marshal.PtrToStructure(structurePointer, typeof(IndexHeader)); + Marshal.FreeHGlobal(structurePointer); + + if(idxHeader.identifier != BlockType.Index) throw new FeatureUnsupportedImageException("Index not found!"); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", + header.indexOffset, idxHeader.entries); + + // Fill in-memory index + index = new List<IndexEntry>(); + for(ushort i = 0; i < idxHeader.entries; i++) + { + IndexEntry entry = new IndexEntry(); + structureBytes = new byte[Marshal.SizeOf(entry)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(entry)); + entry = (IndexEntry)Marshal.PtrToStructure(structurePointer, typeof(IndexEntry)); + Marshal.FreeHGlobal(structurePointer); + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Block type {0} with data type {1} is indexed to be at {2}", entry.blockType, + entry.dataType, entry.offset); + index.Add(entry); + } + + imageInfo.ImageSize = 0; + + bool foundUserDataDdt = false; + mediaTags = new Dictionary<MediaTagType, byte[]>(); + foreach(IndexEntry entry in index) + { + imageStream.Position = (long)entry.offset; + switch(entry.blockType) + { + case BlockType.DataBlock: + // NOP block, skip + if(entry.dataType == DataType.NoData) break; + + imageStream.Position = (long)entry.offset; + + BlockHeader blockHeader = new BlockHeader(); + structureBytes = new byte[Marshal.SizeOf(blockHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); + blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); + Marshal.FreeHGlobal(structurePointer); + imageInfo.ImageSize += blockHeader.cmpLength; + + // Unused, skip + if(entry.dataType == DataType.UserData) + { + if(blockHeader.sectorSize > imageInfo.SectorSize) + imageInfo.SectorSize = blockHeader.sectorSize; + break; + } + + if(blockHeader.identifier != entry.blockType) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect identifier for data block at position {0}", + entry.offset); + break; + } + + if(blockHeader.type != entry.dataType) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Expected block with data type {0} at position {1} but found data type {2}", + entry.dataType, entry.offset, blockHeader.type); + break; + } + + byte[] data; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found data block type {0} at position {1}", entry.dataType, + entry.offset); + + // Decompress media tag + if(blockHeader.compression == CompressionType.Lzma || blockHeader.compression == + CompressionType.LzmaClauniaSubchannelTransform) + { + if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform && + entry.dataType != DataType.CdSectorSubchannel) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Invalid compression type {0} for block with data type {1}, continuing...", + blockHeader.compression, entry.dataType); + break; + } + + DateTime startDecompress = DateTime.Now; + byte[] compressedTag = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; + byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; + imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); + imageStream.Read(compressedTag, 0, compressedTag.Length); + MemoryStream compressedTagMs = new MemoryStream(compressedTag); + LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedTagMs); + data = new byte[blockHeader.length]; + lzmaBlock.Read(data, 0, (int)blockHeader.length); + lzmaBlock.Close(); + compressedTagMs.Close(); + + if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform) + data = ClauniaSubchannelUntransform(data); + + DateTime endDecompress = DateTime.Now; + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Took {0} seconds to decompress block", + (endDecompress - startDecompress).TotalSeconds); + } + else if(blockHeader.compression == CompressionType.None) + { + data = new byte[blockHeader.length]; + imageStream.Read(data, 0, (int)blockHeader.length); + } + else + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found unknown compression type {0}, continuing...", + (ushort)blockHeader.compression); + break; + } + + // Check CRC, if not correct, skip it + Crc64Context.Data(data, out byte[] blockCrc); + if(BitConverter.ToUInt64(blockCrc, 0) != blockHeader.crc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", + BitConverter.ToUInt64(blockCrc, 0), blockHeader.crc64); + break; + } + + // Check if it's not a media tag, but a sector tag, and fill the appropriate table then + switch(entry.dataType) + { + case DataType.CdSectorPrefix: + sectorPrefix = data; + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync); + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader); + break; + case DataType.CdSectorSuffix: + sectorSuffix = data; + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubHeader)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubHeader); + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEcc)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEcc); + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccP)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccP); + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccQ)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccQ); + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEdc)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEdc); + break; + case DataType.CdSectorSubchannel: + sectorSubchannel = data; + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel)) + imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel); + break; + case DataType.AppleProfileTag: + case DataType.AppleSonyTag: + case DataType.PriamDataTowerTag: + sectorSubchannel = data; + if(!imageInfo.ReadableSectorTags.Contains(SectorTagType.AppleSectorTag)) + imageInfo.ReadableSectorTags.Add(SectorTagType.AppleSectorTag); + break; + default: + MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); + + if(mediaTags.ContainsKey(mediaTagType)) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Media tag type {0} duplicated, removing previous entry...", + mediaTagType); + + mediaTags.Remove(mediaTagType); + } + + mediaTags.Add(mediaTagType, data); + break; + } + + break; + case BlockType.DeDuplicationTable: + // Only user data deduplication tables are used right now + if(entry.dataType != DataType.UserData) break; + + DdtHeader ddtHeader = new DdtHeader(); + structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(ddtHeader)); + ddtHeader = (DdtHeader)Marshal.PtrToStructure(structurePointer, typeof(DdtHeader)); + Marshal.FreeHGlobal(structurePointer); + imageInfo.ImageSize += ddtHeader.cmpLength; + + if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; + + imageInfo.Sectors = ddtHeader.entries; + shift = ddtHeader.shift; + + // Check for DDT compression + switch(ddtHeader.compression) + { + case CompressionType.Lzma: + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Decompressing DDT..."); + DateTime ddtStart = DateTime.UtcNow; + byte[] compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; + byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; + imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); + imageStream.Read(compressedDdt, 0, compressedDdt.Length); + MemoryStream compressedDdtMs = new MemoryStream(compressedDdt); + LzmaStream lzmaDdt = new LzmaStream(lzmaProperties, compressedDdtMs); + byte[] decompressedDdt = new byte[ddtHeader.length]; + lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); + lzmaDdt.Close(); + compressedDdtMs.Close(); + userDataDdt = new ulong[ddtHeader.entries]; + for(ulong i = 0; i < ddtHeader.entries; i++) + userDataDdt[i] = BitConverter.ToUInt64(decompressedDdt, (int)(i * sizeof(ulong))); + DateTime ddtEnd = DateTime.UtcNow; + inMemoryDdt = true; + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Took {0} seconds to decompress DDT", + (ddtEnd - ddtStart).TotalSeconds); + break; + case CompressionType.None: + inMemoryDdt = false; + outMemoryDdtPosition = (long)entry.offset; + break; + default: + throw new + ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)ddtHeader.compression}"); + } + + foundUserDataDdt = true; + break; + // Logical geometry block. It doesn't have a CRC coz, well, it's not so important + case BlockType.GeometryBlock: + geometryBlock = new GeometryBlock(); + structureBytes = new byte[Marshal.SizeOf(geometryBlock)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(geometryBlock)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(geometryBlock)); + geometryBlock = (GeometryBlock)Marshal.PtrToStructure(structurePointer, typeof(GeometryBlock)); + Marshal.FreeHGlobal(structurePointer); + if(geometryBlock.identifier == BlockType.GeometryBlock) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Geometry set to {0} cylinders {1} heads {2} sectors per track", + geometryBlock.cylinders, geometryBlock.heads, + geometryBlock.sectorsPerTrack); + imageInfo.Cylinders = geometryBlock.cylinders; + imageInfo.Heads = geometryBlock.heads; + imageInfo.SectorsPerTrack = geometryBlock.sectorsPerTrack; + } + + break; + // Metadata block + case BlockType.MetadataBlock: + MetadataBlock metadataBlock = new MetadataBlock(); + structureBytes = new byte[Marshal.SizeOf(metadataBlock)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(metadataBlock)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(metadataBlock)); + metadataBlock = (MetadataBlock)Marshal.PtrToStructure(structurePointer, typeof(MetadataBlock)); + Marshal.FreeHGlobal(structurePointer); + + if(metadataBlock.identifier != entry.blockType) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect identifier for data block at position {0}", + entry.offset); + break; + } + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found metadata block at position {0}", + entry.offset); + + byte[] metadata = new byte[metadataBlock.blockSize]; + imageStream.Position = (long)entry.offset; + imageStream.Read(metadata, 0, metadata.Length); + + if(metadataBlock.mediaSequence > 0 && metadataBlock.lastMediaSequence > 0) + { + imageInfo.MediaSequence = metadataBlock.mediaSequence; + imageInfo.LastMediaSequence = metadataBlock.lastMediaSequence; + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Setting media sequence as {0} of {1}", imageInfo.MediaSequence, + imageInfo.LastMediaSequence); + } + + if(metadataBlock.creatorLength > 0 && + metadataBlock.creatorLength + metadataBlock.creatorOffset <= metadata.Length) + { + imageInfo.Creator = Encoding.Unicode.GetString(metadata, (int)metadataBlock.creatorOffset, + (int)(metadataBlock.creatorLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting creator: {0}", + imageInfo.Creator); + } + + if(metadataBlock.commentsOffset > 0 && + metadataBlock.commentsLength + metadataBlock.commentsOffset <= metadata.Length) + { + imageInfo.Comments = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.commentsOffset, + (int)(metadataBlock.commentsLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting comments: {0}", + imageInfo.Comments); + } + + if(metadataBlock.mediaTitleOffset > 0 && + metadataBlock.mediaTitleLength + metadataBlock.mediaTitleOffset <= metadata.Length) + { + imageInfo.MediaTitle = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaTitleOffset, + (int)(metadataBlock.mediaTitleLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media title: {0}", + imageInfo.MediaTitle); + } + + if(metadataBlock.mediaManufacturerOffset > 0 && + metadataBlock.mediaManufacturerLength + metadataBlock.mediaManufacturerOffset <= + metadata.Length) + { + imageInfo.MediaManufacturer = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaManufacturerOffset, + (int)(metadataBlock.mediaManufacturerLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media manufacturer: {0}", + imageInfo.MediaManufacturer); + } + + if(metadataBlock.mediaModelOffset > 0 && + metadataBlock.mediaModelLength + metadataBlock.mediaModelOffset <= metadata.Length) + { + imageInfo.MediaModel = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaModelOffset, + (int)(metadataBlock.mediaModelLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media model: {0}", + imageInfo.MediaModel); + } + + if(metadataBlock.mediaSerialNumberOffset > 0 && + metadataBlock.mediaSerialNumberLength + metadataBlock.mediaSerialNumberOffset <= + metadata.Length) + { + imageInfo.MediaSerialNumber = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaSerialNumberOffset, + (int)(metadataBlock.mediaSerialNumberLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media serial number: {0}", + imageInfo.MediaSerialNumber); + } + + if(metadataBlock.mediaBarcodeOffset > 0 && + metadataBlock.mediaBarcodeLength + metadataBlock.mediaBarcodeOffset <= metadata.Length) + { + imageInfo.MediaBarcode = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaBarcodeOffset, + (int)(metadataBlock.mediaBarcodeLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media barcode: {0}", + imageInfo.MediaBarcode); + } + + if(metadataBlock.mediaPartNumberOffset > 0 && + metadataBlock.mediaPartNumberLength + metadataBlock.mediaPartNumberOffset <= metadata.Length) + { + imageInfo.MediaPartNumber = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaPartNumberOffset, + (int)(metadataBlock.mediaPartNumberLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media part number: {0}", + imageInfo.MediaPartNumber); + } + + if(metadataBlock.driveManufacturerOffset > 0 && + metadataBlock.driveManufacturerLength + metadataBlock.driveManufacturerOffset <= + metadata.Length) + { + imageInfo.DriveManufacturer = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveManufacturerOffset, + (int)(metadataBlock.driveManufacturerLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive manufacturer: {0}", + imageInfo.DriveManufacturer); + } + + if(metadataBlock.driveModelOffset > 0 && + metadataBlock.driveModelLength + metadataBlock.driveModelOffset <= metadata.Length) + { + imageInfo.DriveModel = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveModelOffset, + (int)(metadataBlock.driveModelLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive model: {0}", + imageInfo.DriveModel); + } + + if(metadataBlock.driveSerialNumberOffset > 0 && + metadataBlock.driveSerialNumberLength + metadataBlock.driveSerialNumberOffset <= + metadata.Length) + { + imageInfo.DriveSerialNumber = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveSerialNumberOffset, + (int)(metadataBlock.driveSerialNumberLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive serial number: {0}", + imageInfo.DriveSerialNumber); + } + + if(metadataBlock.driveFirmwareRevisionOffset > 0 && metadataBlock.driveFirmwareRevisionLength + + metadataBlock.driveFirmwareRevisionOffset <= metadata.Length) + { + imageInfo.DriveFirmwareRevision = + Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveFirmwareRevisionOffset, + (int)(metadataBlock.driveFirmwareRevisionLength - 2)); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Setting drive firmware revision: {0}", + imageInfo.DriveFirmwareRevision); + } + + break; + // Optical disc tracks block + case BlockType.TracksBlock: + TracksHeader tracksHeader = new TracksHeader(); + structureBytes = new byte[Marshal.SizeOf(tracksHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(tracksHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(tracksHeader)); + tracksHeader = (TracksHeader)Marshal.PtrToStructure(structurePointer, typeof(TracksHeader)); + Marshal.FreeHGlobal(structurePointer); + if(tracksHeader.identifier != BlockType.TracksBlock) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect identifier for tracks block at position {0}", + entry.offset); + break; + } + + structureBytes = new byte[Marshal.SizeOf(typeof(TrackEntry)) * tracksHeader.entries]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + Crc64Context.Data(structureBytes, out byte[] trksCrc); + if(BitConverter.ToUInt64(trksCrc, 0) != tracksHeader.crc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", + BitConverter.ToUInt64(trksCrc, 0), tracksHeader.crc64); + break; + } + + imageStream.Position -= structureBytes.Length; + + Tracks = new List<Track>(); + trackFlags = new Dictionary<byte, byte>(); + trackIsrcs = new Dictionary<byte, string>(); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found {0} tracks at position {0}", + tracksHeader.entries, entry.offset); + + for(ushort i = 0; i < tracksHeader.entries; i++) + { + TrackEntry trackEntry = new TrackEntry(); + structureBytes = new byte[Marshal.SizeOf(trackEntry)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trackEntry)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(trackEntry)); + trackEntry = (TrackEntry)Marshal.PtrToStructure(structurePointer, typeof(TrackEntry)); + Marshal.FreeHGlobal(structurePointer); + + Tracks.Add(new Track + { + TrackSequence = trackEntry.sequence, + TrackType = trackEntry.type, + TrackStartSector = (ulong)trackEntry.start, + TrackEndSector = (ulong)trackEntry.end, + TrackPregap = (ulong)trackEntry.pregap, + TrackSession = trackEntry.session, + TrackFile = imageFilter.GetFilename(), + TrackFileType = "BINARY", + TrackFilter = imageFilter + }); + + trackFlags.Add(trackEntry.sequence, trackEntry.flags); + trackIsrcs.Add(trackEntry.sequence, trackEntry.isrc); + } + + imageInfo.HasPartitions = true; + imageInfo.HasSessions = true; + break; + // CICM XML metadata block + case BlockType.CicmBlock: + CicmMetadataBlock cicmBlock = new CicmMetadataBlock(); + structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(cicmBlock)); + cicmBlock = (CicmMetadataBlock)Marshal.PtrToStructure(structurePointer, + typeof(CicmMetadataBlock)); + Marshal.FreeHGlobal(structurePointer); + if(cicmBlock.identifier != BlockType.CicmBlock) break; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found CICM XML metadata block at position {0}", entry.offset); + + byte[] cicmBytes = new byte[cicmBlock.length]; + imageStream.Read(cicmBytes, 0, cicmBytes.Length); + MemoryStream cicmMs = new MemoryStream(cicmBytes); + XmlSerializer cicmXs = new XmlSerializer(typeof(CICMMetadataType)); + try + { + StreamReader sr = new StreamReader(cicmMs); + CicmMetadata = (CICMMetadataType)cicmXs.Deserialize(sr); + sr.Close(); + } + catch(XmlException ex) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Exception {0} processing CICM XML metadata block", ex.Message); + CicmMetadata = null; + } + + break; + // Dump hardware block + case BlockType.DumpHardwareBlock: + DumpHardwareHeader dumpBlock = new DumpHardwareHeader(); + structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpBlock)); + dumpBlock = (DumpHardwareHeader)Marshal.PtrToStructure(structurePointer, + typeof(DumpHardwareHeader)); + Marshal.FreeHGlobal(structurePointer); + if(dumpBlock.identifier != BlockType.DumpHardwareBlock) break; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found dump hardware block at position {0}", entry.offset); + + structureBytes = new byte[dumpBlock.length]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + Crc64Context.Data(structureBytes, out byte[] dumpCrc); + if(BitConverter.ToUInt64(dumpCrc, 0) != dumpBlock.crc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", + BitConverter.ToUInt64(dumpCrc, 0), dumpBlock.crc64); + break; + } + + imageStream.Position -= structureBytes.Length; + + DumpHardware = new List<DumpHardwareType>(); + + for(ushort i = 0; i < dumpBlock.entries; i++) + { + DumpHardwareEntry dumpEntry = new DumpHardwareEntry(); + structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpEntry)); + dumpEntry = (DumpHardwareEntry)Marshal.PtrToStructure(structurePointer, + typeof(DumpHardwareEntry)); + Marshal.FreeHGlobal(structurePointer); + + DumpHardwareType dump = new DumpHardwareType + { + Software = new SoftwareType(), + Extents = new ExtentType[dumpEntry.extents] + }; + + byte[] tmp; + + if(dumpEntry.manufacturerLength > 0) + { + tmp = new byte[dumpEntry.manufacturerLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Manufacturer = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.modelLength > 0) + { + tmp = new byte[dumpEntry.modelLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Model = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.revisionLength > 0) + { + tmp = new byte[dumpEntry.revisionLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Revision = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.firmwareLength > 0) + { + tmp = new byte[dumpEntry.firmwareLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Firmware = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.serialLength > 0) + { + tmp = new byte[dumpEntry.serialLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Serial = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.softwareNameLength > 0) + { + tmp = new byte[dumpEntry.softwareNameLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Software.Name = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.softwareVersionLength > 0) + { + tmp = new byte[dumpEntry.softwareVersionLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Software.Version = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.softwareOperatingSystemLength > 0) + { + tmp = new byte[dumpEntry.softwareOperatingSystemLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Software.OperatingSystem = Encoding.UTF8.GetString(tmp); + } + + tmp = new byte[16]; + for(uint j = 0; j < dumpEntry.extents; j++) + { + imageStream.Read(tmp, 0, tmp.Length); + dump.Extents[j] = new ExtentType + { + Start = BitConverter.ToUInt64(tmp, 0), + End = BitConverter.ToUInt64(tmp, 8) + }; + } + + dump.Extents = dump.Extents.OrderBy(t => t.Start).ToArray(); + if(dump.Extents.Length > 0) DumpHardware.Add(dump); + } + + if(DumpHardware.Count == 0) DumpHardware = null; + break; + } + } + + if(!foundUserDataDdt) throw new ImageNotSupportedException("Could not find user data deduplication table."); + + imageInfo.CreationTime = DateTime.FromFileTimeUtc(header.creationTime); + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Image created on {0}", imageInfo.CreationTime); + imageInfo.LastModificationTime = DateTime.FromFileTimeUtc(header.lastWrittenTime); + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Image last written on {0}", + imageInfo.LastModificationTime); + + if(geometryBlock.identifier != BlockType.GeometryBlock && imageInfo.XmlMediaType == XmlMediaType.BlockMedia) + { + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + } + + imageInfo.XmlMediaType = GetXmlMediaType(header.mediaType); + imageInfo.ReadableMediaTags.AddRange(mediaTags.Keys); + + // Initialize caches + blockCache = new Dictionary<ulong, byte[]>(); + blockHeaderCache = new Dictionary<ulong, BlockHeader>(); + currentCacheSize = 0; + if(!inMemoryDdt) ddtEntryCache = new Dictionary<ulong, ulong>(); + + // Initialize tracks, sessions and partitions + if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) + { + if(Tracks == null || Tracks.Count == 0) + { + Tracks = new List<Track> + { + new Track + { + Indexes = new Dictionary<int, ulong>(), + TrackBytesPerSector = (int)imageInfo.SectorSize, + TrackEndSector = imageInfo.Sectors - 1, + TrackFile = imageFilter.GetFilename(), + TrackFileType = "BINARY", + TrackFilter = imageFilter, + TrackRawBytesPerSector = (int)imageInfo.SectorSize, + TrackSession = 1, + TrackSequence = 1, + TrackType = TrackType.Data + } + }; + + trackFlags = new Dictionary<byte, byte> {{1, (byte)CdFlags.DataTrack}}; + trackIsrcs = new Dictionary<byte, string>(); + } + + Sessions = new List<Session>(); + for(int i = 1; i <= Tracks.Max(t => t.TrackSession); i++) + Sessions.Add(new Session + { + SessionSequence = (ushort)i, + StartTrack = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackSequence), + EndTrack = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackSequence), + StartSector = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackStartSector), + EndSector = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackEndSector) + }); + + ulong currentTrackOffset = 0; + Partitions = new List<Partition>(); + foreach(Track track in Tracks.OrderBy(t => t.TrackStartSector)) + { + Partitions.Add(new Partition + { + Sequence = track.TrackSequence, + Type = track.TrackType.ToString(), + Name = $"Track {track.TrackSequence}", + Offset = currentTrackOffset, + Start = track.TrackStartSector, + Size = (track.TrackEndSector - track.TrackStartSector + 1) * + (ulong)track.TrackBytesPerSector, + Length = track.TrackEndSector - track.TrackStartSector + 1, + Scheme = "Optical disc track" + }); + currentTrackOffset += (track.TrackEndSector - track.TrackStartSector + 1) * + (ulong)track.TrackBytesPerSector; + } + + Track[] tracks = Tracks.ToArray(); + for(int i = 0; i < tracks.Length; i++) + { + byte[] sector = ReadSector(tracks[i].TrackStartSector); + tracks[i].TrackBytesPerSector = sector.Length; + tracks[i].TrackRawBytesPerSector = + sectorPrefix != null && sectorSuffix != null ? 2352 : sector.Length; + + if(sectorSubchannel == null) continue; + + tracks[i].TrackSubchannelFile = tracks[i].TrackFile; + tracks[i].TrackSubchannelFilter = tracks[i].TrackFilter; + tracks[i].TrackSubchannelType = TrackSubchannelType.Raw; + } + + Tracks = tracks.ToList(); + } + else + { + Tracks = null; + Sessions = null; + Partitions = null; + } + + SetMetadataFromTags(); + + return true; + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + if(mediaTags.TryGetValue(tag, out byte[] data)) return data; + + throw new FeatureNotPresentImageException("Requested tag is not present in image"); + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + ulong ddtEntry = GetDdtEntry(sectorAddress); + uint offsetMask = (uint)((1 << shift) - 1); + ulong offset = ddtEntry & offsetMask; + ulong blockOffset = ddtEntry >> shift; + + // Partially written image... as we can't know the real sector size just assume it's common :/ + if(ddtEntry == 0) return new byte[imageInfo.SectorSize]; + + byte[] sector; + + // Check if block is cached + if(blockCache.TryGetValue(blockOffset, out byte[] block) && + blockHeaderCache.TryGetValue(blockOffset, out BlockHeader blockHeader)) + { + sector = new byte[blockHeader.sectorSize]; + Array.Copy(block, (long)(offset * blockHeader.sectorSize), sector, 0, blockHeader.sectorSize); + return sector; + } + + // Read block header + imageStream.Position = (long)blockOffset; + blockHeader = new BlockHeader(); + structureBytes = new byte[Marshal.SizeOf(blockHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); + blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); + Marshal.FreeHGlobal(structurePointer); + + // Decompress block + switch(blockHeader.compression) + { + case CompressionType.None: + block = new byte[blockHeader.length]; + imageStream.Read(block, 0, (int)blockHeader.length); + break; + case CompressionType.Lzma: + byte[] compressedBlock = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; + byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; + imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); + imageStream.Read(compressedBlock, 0, compressedBlock.Length); + MemoryStream compressedBlockMs = new MemoryStream(compressedBlock); + LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedBlockMs); + block = new byte[blockHeader.length]; + lzmaBlock.Read(block, 0, (int)blockHeader.length); + lzmaBlock.Close(); + compressedBlockMs.Close(); + break; + case CompressionType.Flac: + byte[] flacBlock = new byte[blockHeader.cmpLength]; + imageStream.Read(flacBlock, 0, flacBlock.Length); + MemoryStream flacMs = new MemoryStream(flacBlock); + FlakeReader flakeReader = new FlakeReader("", flacMs); + block = new byte[blockHeader.length]; + int samples = (int)(block.Length / blockHeader.sectorSize * 588); + AudioBuffer audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, block, samples); + flakeReader.Read(audioBuffer, samples); + flakeReader.Close(); + flacMs.Close(); + break; + default: + throw new + ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)blockHeader.compression}"); + } + + // Check if cache needs to be emptied + if(currentCacheSize + blockHeader.length >= MAX_CACHE_SIZE) + { + currentCacheSize = 0; + blockHeaderCache = new Dictionary<ulong, BlockHeader>(); + blockCache = new Dictionary<ulong, byte[]>(); + } + + // Add block to cache + currentCacheSize += blockHeader.length; + blockHeaderCache.Add(blockOffset, blockHeader); + blockCache.Add(blockOffset, block); + + sector = new byte[blockHeader.sectorSize]; + Array.Copy(block, (long)(offset * blockHeader.sectorSize), sector, 0, blockHeader.sectorSize); + return sector; + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + return ReadSectorsTag(sectorAddress, 1, tag); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + return ReadSector(trk.TrackStartSector + sectorAddress); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + return ReadSectorTag(trk.TrackStartSector + sectorAddress, tag); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + uint sectorOffset; + uint sectorSize; + uint sectorSkip; + byte[] dataSource; + + if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) + { + Track trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && + sectorAddress <= t.TrackEndSector); + if(trk.TrackSequence == 0) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + "Can't found track containing requested sector"); + + if(trk.TrackType == TrackType.Data) + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + + switch(tag) + { + case SectorTagType.CdSectorEcc: + case SectorTagType.CdSectorEccP: + case SectorTagType.CdSectorEccQ: + case SectorTagType.CdSectorEdc: + case SectorTagType.CdSectorHeader: + case SectorTagType.CdSectorSubchannel: + case SectorTagType.CdSectorSubHeader: + case SectorTagType.CdSectorSync: break; + case SectorTagType.CdTrackFlags: + return trackFlags.TryGetValue((byte)trk.TrackSequence, out byte flags) ? new[] {flags} : null; + case SectorTagType.CdTrackIsrc: + return trackIsrcs.TryGetValue((byte)trk.TrackSequence, out string isrc) + ? Encoding.UTF8.GetBytes(isrc) + : null; + default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + + switch(trk.TrackType) + { + case TrackType.CdMode1: + switch(tag) + { + case SectorTagType.CdSectorSync: + { + sectorOffset = 0; + sectorSize = 12; + sectorSkip = 4; + dataSource = sectorPrefix; + break; + } + case SectorTagType.CdSectorHeader: + { + sectorOffset = 12; + sectorSize = 4; + sectorSkip = 2336; + dataSource = sectorPrefix; + break; + } + case SectorTagType.CdSectorSubHeader: + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + case SectorTagType.CdSectorEcc: + { + sectorOffset = 12; + sectorSize = 276; + sectorSkip = 0; + dataSource = sectorSuffix; + break; + } + case SectorTagType.CdSectorEccP: + { + sectorOffset = 12; + sectorSize = 172; + sectorSkip = 104; + dataSource = sectorSuffix; + break; + } + case SectorTagType.CdSectorEccQ: + { + sectorOffset = 184; + sectorSize = 104; + sectorSkip = 0; + dataSource = sectorSuffix; + break; + } + case SectorTagType.CdSectorEdc: + { + sectorOffset = 0; + sectorSize = 4; + sectorSkip = 284; + dataSource = sectorSuffix; + break; + } + case SectorTagType.CdSectorSubchannel: + { + sectorOffset = 0; + sectorSize = 96; + sectorSkip = 0; + dataSource = sectorSubchannel; + break; + } + default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + + break; + case TrackType.CdMode2Formless: + case TrackType.CdMode2Form1: + case TrackType.CdMode2Form2: + { + switch(tag) + { + case SectorTagType.CdSectorSync: + { + sectorOffset = 0; + sectorSize = 12; + sectorSkip = 4; + dataSource = sectorPrefix; + break; + } + case SectorTagType.CdSectorHeader: + { + sectorOffset = 12; + sectorSize = 4; + sectorSkip = 2336; + dataSource = sectorPrefix; + break; + } + // These could be implemented + case SectorTagType.CdSectorEcc: + case SectorTagType.CdSectorEccP: + case SectorTagType.CdSectorEccQ: + case SectorTagType.CdSectorSubHeader: + case SectorTagType.CdSectorEdc: + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + case SectorTagType.CdSectorSubchannel: + { + sectorOffset = 0; + sectorSize = 96; + sectorSkip = 0; + dataSource = sectorSubchannel; + break; + } + default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + + break; + } + case TrackType.Audio: + { + switch(tag) + { + case SectorTagType.CdSectorSubchannel: + { + sectorOffset = 0; + sectorSize = 96; + sectorSkip = 0; + dataSource = sectorSubchannel; + break; + } + default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + + break; + } + default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); + } + } + else throw new FeatureNotPresentImageException("Feature not present in image"); + + if(dataSource == null) throw new ArgumentException("Unsupported tag requested", nameof(tag)); + + byte[] data = new byte[sectorSize * length]; + + if(sectorOffset == 0 && sectorSkip == 0) + { + Array.Copy(dataSource, (long)(sectorAddress * sectorSize), data, 0, length * sectorSize); + return data; + } + + for(int i = 0; i < length; i++) + Array.Copy(dataSource, (long)(sectorAddress * (sectorOffset + sectorSize + sectorSkip)), data, + i * sectorSize, sectorSize); + + return data; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + return ReadSectors(trk.TrackStartSector + sectorAddress, length); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + return ReadSectorsTag(trk.TrackStartSector + sectorAddress, length, tag); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + switch(imageInfo.XmlMediaType) + { + case XmlMediaType.OpticalDisc: + Track trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && + sectorAddress <= t.TrackEndSector); + if(trk.TrackSequence == 0) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + "Can't found track containing requested sector"); + + if(sectorSuffix == null || sectorPrefix == null) return ReadSector(sectorAddress); + + byte[] sector = new byte[2352]; + byte[] data = ReadSector(sectorAddress); + + switch(trk.TrackType) + { + case TrackType.Audio: + case TrackType.Data: return data; + case TrackType.CdMode1: + Array.Copy(sectorPrefix, (int)sectorAddress * 16, sector, 0, 16); + Array.Copy(data, 0, sector, 16, 2048); + Array.Copy(sectorSuffix, (int)sectorAddress * 288, sector, 2064, 288); + return sector; + case TrackType.CdMode2Formless: + case TrackType.CdMode2Form1: + case TrackType.CdMode2Form2: + Array.Copy(sectorPrefix, (int)sectorAddress * 16, sector, 0, 16); + Array.Copy(data, 0, sector, 16, 2336); + return sector; + } + + break; + case XmlMediaType.BlockMedia: + switch(imageInfo.MediaType) + { + case MediaType.AppleFileWare: + case MediaType.AppleProfile: + case MediaType.AppleSonySS: + case MediaType.AppleSonyDS: + case MediaType.AppleWidget: + case MediaType.PriamDataTower: return ReadSectorsLong(sectorAddress, 1); + } + + break; + } + + throw new FeatureNotPresentImageException("Feature not present in image"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + return ReadSectorLong(trk.TrackStartSector + sectorAddress); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + byte[] sectors; + byte[] data; + + switch(imageInfo.XmlMediaType) + { + case XmlMediaType.OpticalDisc: + Track trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && + sectorAddress <= t.TrackEndSector); + if(trk.TrackSequence == 0) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + "Can't found track containing requested sector"); + + if(sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + switch(trk.TrackType) + { + // These types only contain user data + case TrackType.Audio: + case TrackType.Data: return ReadSectors(sectorAddress, length); + // Join prefix (sync, header) with user data with suffix (edc, ecc p, ecc q) + case TrackType.CdMode1: + if(sectorPrefix == null || sectorSuffix == null) return ReadSectors(sectorAddress, length); + + sectors = new byte[2352 * length]; + data = ReadSectors(sectorAddress, length); + for(uint i = 0; i < length; i++) + { + Array.Copy(sectorPrefix, (int)((sectorAddress + i) * 16), sectors, (int)(i * 2352), + 16); + Array.Copy(data, (int)(i * 2048), sectors, + (int)(i * 2352) + 16, 2048); + Array.Copy(sectorSuffix, (int)((sectorAddress + i) * 288), sectors, + (int)(i * 2352) + 2064, 288); + } + + return sectors; + // Join prefix (sync, header) with user data + case TrackType.CdMode2Formless: + case TrackType.CdMode2Form1: + case TrackType.CdMode2Form2: + if(sectorPrefix == null || sectorSuffix == null) return ReadSectors(sectorAddress, length); + + sectors = new byte[2352 * length]; + data = ReadSectors(sectorAddress, length); + for(uint i = 0; i < length; i++) + { + Array.Copy(sectorPrefix, (int)((sectorAddress + i) * 16), sectors, (int)(i * 2352), + 16); + Array.Copy(data, (int)(i * 2336), sectors, + (int)(i * 2352) + 16, 2336); + } + + return sectors; + } + + break; + case XmlMediaType.BlockMedia: + switch(imageInfo.MediaType) + { + // Join user data with tags + case MediaType.AppleFileWare: + case MediaType.AppleProfile: + case MediaType.AppleSonySS: + case MediaType.AppleSonyDS: + case MediaType.AppleWidget: + case MediaType.PriamDataTower: + if(sectorSubchannel == null) return ReadSector(sectorAddress); + + uint tagSize = 0; + switch(imageInfo.MediaType) + { + case MediaType.AppleFileWare: + case MediaType.AppleProfile: + case MediaType.AppleWidget: + tagSize = 20; + break; + case MediaType.AppleSonySS: + case MediaType.AppleSonyDS: + tagSize = 12; + break; + case MediaType.PriamDataTower: + tagSize = 24; + break; + } + + uint sectorSize = 512 + tagSize; + data = ReadSectors(sectorAddress, length); + sectors = new byte[(sectorSize + 512) * length]; + for(uint i = 0; i < length; i++) + { + Array.Copy(sectorSubchannel, (int)((sectorAddress + i) * tagSize), sectors, + (int)(i * sectorSize + 512), tagSize); + Array.Copy(data, (int)((sectorAddress + i) * 512), sectors, (int)(i * 512), 512); + } + + return sectors; + } + + break; + } + + throw new FeatureNotPresentImageException("Feature not present in image"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + return ReadSectorsLong(trk.TrackStartSector + sectorAddress, length); + } + + public List<Track> GetSessionTracks(Session session) + { + return Tracks.Where(t => t.TrackSequence == session.SessionSequence).ToList(); + } + + public List<Track> GetSessionTracks(ushort session) + { + return Tracks.Where(t => t.TrackSequence == session).ToList(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Structs.cs b/DiscImageChef.DiscImages/DiscImageChef/Structs.cs new file mode 100644 index 000000000..b0bcc8fa7 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Structs.cs @@ -0,0 +1,326 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + /// <summary>Header, at start of file</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] + struct DicHeader + { + /// <summary>Header identifier, <see cref="DIC_MAGIC" /></summary> + public ulong identifier; + /// <summary>UTF-16LE name of the application that created the image</summary> + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string application; + /// <summary>Image format major version. A new major version means a possibly incompatible change of format</summary> + public byte imageMajorVersion; + /// <summary>Image format minor version. A new minor version indicates a compatible change of format</summary> + public byte imageMinorVersion; + /// <summary>Major version of the application that created the image</summary> + public byte applicationMajorVersion; + /// <summary>Minor version of the application that created the image</summary> + public byte applicationMinorVersion; + /// <summary>Type of media contained on image</summary> + public MediaType mediaType; + /// <summary>Offset to index</summary> + public ulong indexOffset; + /// <summary>Windows filetime (100 nanoseconds since 1601/01/01 00:00:00 UTC) of image creation time</summary> + public long creationTime; + /// <summary>Windows filetime (100 nanoseconds since 1601/01/01 00:00:00 UTC) of image last written time</summary> + public long lastWrittenTime; + } + + /// <summary>Header for a deduplication table. Table follows it</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DdtHeader + { + /// <summary>Identifier, <see cref="BlockType.DeDuplicationTable" /></summary> + public BlockType identifier; + /// <summary>Type of data pointed by this DDT</summary> + public DataType type; + /// <summary>Compression algorithm used to compress the DDT</summary> + public CompressionType compression; + /// <summary>Each entry is ((byte offset in file) << shift) + (sector offset in block)</summary> + public byte shift; + /// <summary>How many entries are in the table</summary> + public ulong entries; + /// <summary>Compressed length for the DDT</summary> + public ulong cmpLength; + /// <summary>Uncompressed length for the DDT</summary> + public ulong length; + /// <summary>CRC64-ECMA of the compressed DDT</summary> + public ulong cmpCrc64; + /// <summary>CRC64-ECMA of the uncompressed DDT</summary> + public ulong crc64; + } + + /// <summary>Header for the index, followed by entries</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct IndexHeader + { + /// <summary>Identifier, <see cref="BlockType.Index" /></summary> + public BlockType identifier; + /// <summary>How many entries follow this header</summary> + public ushort entries; + /// <summary>CRC64-ECMA of the index</summary> + public ulong crc64; + } + + /// <summary>Index entry</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct IndexEntry + { + /// <summary>Type of item pointed by this entry</summary> + public BlockType blockType; + /// <summary>Type of data contained by the block pointed by this entry</summary> + public DataType dataType; + /// <summary>Offset in file where item is stored</summary> + public ulong offset; + } + + /// <summary>Block header, precedes block data</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct BlockHeader + { + /// <summary>Identifier, <see cref="BlockType.DataBlock" /></summary> + public BlockType identifier; + /// <summary>Type of data contained by this block</summary> + public DataType type; + /// <summary>Compression algorithm used to compress the block</summary> + public CompressionType compression; + /// <summary>Size in bytes of each sector contained in this block</summary> + public uint sectorSize; + /// <summary>Compressed length for the block</summary> + public uint cmpLength; + /// <summary>Uncompressed length for the block</summary> + public uint length; + /// <summary>CRC64-ECMA of the compressed block</summary> + public ulong cmpCrc64; + /// <summary>CRC64-ECMA of the uncompressed block</summary> + public ulong crc64; + } + + /// <summary>Geometry block, contains physical geometry information</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct GeometryBlock + { + /// <summary>Identifier, <see cref="BlockType.GeometryBlock" /></summary> + public BlockType identifier; + public uint cylinders; + public uint heads; + public uint sectorsPerTrack; + } + + /// <summary>Metadata block, contains metadata</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MetadataBlock + { + /// <summary>Identifier, <see cref="BlockType.MetadataBlock" /></summary> + public BlockType identifier; + /// <summary>Size in bytes of this whole metadata block</summary> + public uint blockSize; + /// <summary>Sequence of media set this media belongs to</summary> + public int mediaSequence; + /// <summary>Total number of media on the media set this media belongs to</summary> + public int lastMediaSequence; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint creatorOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint creatorLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint commentsOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint commentsLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint mediaTitleOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint mediaTitleLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint mediaManufacturerOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint mediaManufacturerLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint mediaModelOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint mediaModelLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint mediaSerialNumberOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint mediaSerialNumberLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint mediaBarcodeOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint mediaBarcodeLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint mediaPartNumberOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint mediaPartNumberLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint driveManufacturerOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint driveManufacturerLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint driveModelOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint driveModelLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint driveSerialNumberOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint driveSerialNumberLength; + /// <summary>Offset to start of creator string from start of this block</summary> + public uint driveFirmwareRevisionOffset; + /// <summary>Length in bytes of the null-terminated UTF-16LE creator string</summary> + public uint driveFirmwareRevisionLength; + } + + /// <summary>Contains list of optical disc tracks</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct TracksHeader + { + /// <summary>Identifier, <see cref="BlockType.TracksBlock" /></summary> + public BlockType identifier; + /// <summary>How many entries follow this header</summary> + public ushort entries; + /// <summary>CRC64-ECMA of the block</summary> + public ulong crc64; + } + + /// <summary>Optical disc track</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] + struct TrackEntry + { + /// <summary>Track sequence</summary> + public byte sequence; + /// <summary>Track type</summary> + public TrackType type; + /// <summary>Track starting LBA</summary> + public long start; + /// <summary>Track last LBA</summary> + public long end; + /// <summary>Track pregap in sectors</summary> + public long pregap; + /// <summary>Track session</summary> + public byte session; + /// <summary>Track's ISRC in ASCII</summary> + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] + public string isrc; + /// <summary>Track flags</summary> + public byte flags; + } + + /// <summary>Geometry block, contains physical geometry information</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CicmMetadataBlock + { + /// <summary>Identifier, <see cref="BlockType.CicmBlock" /></summary> + public BlockType identifier; + public uint length; + } + + /// <summary>Dump hardware block, contains a list of hardware used to dump the media on this image</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DumpHardwareHeader + { + /// <summary>Identifier, <see cref="BlockType.DumpHardwareBlock" /></summary> + public BlockType identifier; + /// <summary>How many entries follow this header</summary> + public ushort entries; + /// <summary>Size of the whole block, not including this header, in bytes</summary> + public uint length; + /// <summary>CRC64-ECMA of the block</summary> + public ulong crc64; + } + + /// <summary>Dump hardware entry, contains length of strings that follow, in the same order as the length, this structure</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DumpHardwareEntry + { + /// <summary>Length of UTF-8 manufacturer string</summary> + public uint manufacturerLength; + /// <summary>Length of UTF-8 model string</summary> + public uint modelLength; + /// <summary>Length of UTF-8 revision string</summary> + public uint revisionLength; + /// <summary>Length of UTF-8 firmware version string</summary> + public uint firmwareLength; + /// <summary>Length of UTF-8 serial string</summary> + public uint serialLength; + /// <summary>Length of UTF-8 software name string</summary> + public uint softwareNameLength; + /// <summary>Length of UTF-8 software version string</summary> + public uint softwareVersionLength; + /// <summary>Length of UTF-8 software operating system string</summary> + public uint softwareOperatingSystemLength; + /// <summary>How many extents are after the strings</summary> + public uint extents; + } + + /// <summary>Dump hardware extent, first and last sector dumped, both inclusive</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DumpHardwareExtent + { + public ulong start; + public ulong end; + } + + /// <summary> + /// Checksum block, contains a checksum of all user data sectors (except for optical discs that is 2352 bytes raw + /// sector if available + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChecksumHeader + { + /// <summary>Identifier, <see cref="BlockType.ChecksumBlock" /></summary> + public BlockType identifier; + /// <summary>Length in bytes of the block</summary> + public uint length; + /// <summary>How many checksums follow</summary> + public byte entries; + } + + /// <summary>Checksum entry, followed by checksum data itself</summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChecksumEntry + { + /// <summary>Checksum algorithm</summary> + public ChecksumAlgorithm type; + /// <summary>Length in bytes of checksum that follows this structure</summary> + public uint length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Verify.cs b/DiscImageChef.DiscImages/DiscImageChef/Verify.cs new file mode 100644 index 000000000..eba5823c0 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Verify.cs @@ -0,0 +1,321 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Verify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Verifies DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + public bool? VerifySector(ulong sectorAddress) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) return null; + + byte[] buffer = ReadSectorLong(sectorAddress); + return CdChecksums.CheckCdSector(buffer); + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) return null; + + byte[] buffer = ReadSectorLong(sectorAddress, track); + return CdChecksums.CheckCdSector(buffer); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + // Right now only CompactDisc sectors are verifyable + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + { + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + byte[] buffer = ReadSectorsLong(sectorAddress, length); + int bps = (int)(buffer.Length / length); + byte[] sector = new byte[bps]; + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(int i = 0; i < length; i++) + { + Array.Copy(buffer, i * bps, sector, 0, bps); + bool? sectorStatus = CdChecksums.CheckCdSector(sector); + + switch(sectorStatus) + { + case null: + unknownLbas.Add((ulong)i + sectorAddress); + break; + case false: + failingLbas.Add((ulong)i + sectorAddress); + break; + } + } + + if(unknownLbas.Count > 0) return null; + + return failingLbas.Count <= 0; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + // Right now only CompactDisc sectors are verifyable + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + byte[] buffer = ReadSectorsLong(sectorAddress, length, track); + int bps = (int)(buffer.Length / length); + byte[] sector = new byte[bps]; + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(int i = 0; i < length; i++) + { + Array.Copy(buffer, i * bps, sector, 0, bps); + bool? sectorStatus = CdChecksums.CheckCdSector(sector); + + switch(sectorStatus) + { + case null: + unknownLbas.Add((ulong)i + sectorAddress); + break; + case false: + failingLbas.Add((ulong)i + sectorAddress); + break; + } + } + + if(unknownLbas.Count > 0) return null; + + return failingLbas.Count <= 0; + } + + public bool? VerifyMediaImage() + { + // This will traverse all blocks and check their CRC64 without uncompressing them + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Checking index integrity at {0}", + header.indexOffset); + imageStream.Position = (long)header.indexOffset; + + IndexHeader idxHeader = new IndexHeader(); + structureBytes = new byte[Marshal.SizeOf(idxHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(idxHeader)); + idxHeader = (IndexHeader)Marshal.PtrToStructure(structurePointer, typeof(IndexHeader)); + Marshal.FreeHGlobal(structurePointer); + + if(idxHeader.identifier != BlockType.Index) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Incorrect index identifier"); + return false; + } + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", + header.indexOffset, idxHeader.entries); + + structureBytes = new byte[Marshal.SizeOf(typeof(IndexEntry)) * idxHeader.entries]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + Crc64Context.Data(structureBytes, out byte[] verifyCrc); + + if(BitConverter.ToUInt64(verifyCrc, 0) != idxHeader.crc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Expected index CRC {0:X16} but got {1:X16}", + idxHeader.crc64, BitConverter.ToUInt64(verifyCrc, 0)); + return false; + } + + imageStream.Position -= structureBytes.Length; + + List<IndexEntry> vrIndex = new List<IndexEntry>(); + for(ushort i = 0; i < idxHeader.entries; i++) + { + IndexEntry entry = new IndexEntry(); + structureBytes = new byte[Marshal.SizeOf(entry)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(entry)); + entry = (IndexEntry)Marshal.PtrToStructure(structurePointer, typeof(IndexEntry)); + Marshal.FreeHGlobal(structurePointer); + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Block type {0} with data type {1} is indexed to be at {2}", entry.blockType, + entry.dataType, entry.offset); + vrIndex.Add(entry); + } + + // Read up to 1MiB at a time for verification + const int VERIFY_SIZE = 1024 * 1024; + + foreach(IndexEntry entry in vrIndex) + { + imageStream.Position = (long)entry.offset; + Crc64Context crcVerify; + ulong readBytes; + byte[] verifyBytes; + + switch(entry.blockType) + { + case BlockType.DataBlock: + BlockHeader blockHeader = new BlockHeader(); + structureBytes = new byte[Marshal.SizeOf(blockHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); + blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); + Marshal.FreeHGlobal(structurePointer); + + crcVerify = new Crc64Context(); + readBytes = 0; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Verifying data block type {0} at position {1}", entry.dataType, + entry.offset); + + while(readBytes + VERIFY_SIZE < blockHeader.cmpLength) + { + verifyBytes = new byte[VERIFY_SIZE]; + imageStream.Read(verifyBytes, 0, verifyBytes.Length); + crcVerify.Update(verifyBytes); + readBytes += (ulong)verifyBytes.LongLength; + } + + verifyBytes = new byte[blockHeader.cmpLength - readBytes]; + imageStream.Read(verifyBytes, 0, verifyBytes.Length); + crcVerify.Update(verifyBytes); + + verifyCrc = crcVerify.Final(); + + if(BitConverter.ToUInt64(verifyCrc, 0) != blockHeader.cmpCrc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Expected block CRC {0:X16} but got {1:X16}", + blockHeader.cmpCrc64, BitConverter.ToUInt64(verifyCrc, 0)); + return false; + } + + break; + case BlockType.DeDuplicationTable: + DdtHeader ddtHeader = new DdtHeader(); + structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(ddtHeader)); + ddtHeader = (DdtHeader)Marshal.PtrToStructure(structurePointer, typeof(DdtHeader)); + Marshal.FreeHGlobal(structurePointer); + + crcVerify = new Crc64Context(); + readBytes = 0; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Verifying deduplication table type {0} at position {1}", + entry.dataType, entry.offset); + + while(readBytes + VERIFY_SIZE < ddtHeader.cmpLength) + { + verifyBytes = new byte[readBytes]; + imageStream.Read(verifyBytes, 0, verifyBytes.Length); + crcVerify.Update(verifyBytes); + readBytes += (ulong)verifyBytes.LongLength; + } + + verifyBytes = new byte[ddtHeader.cmpLength - readBytes]; + imageStream.Read(verifyBytes, 0, verifyBytes.Length); + crcVerify.Update(verifyBytes); + + verifyCrc = crcVerify.Final(); + + if(BitConverter.ToUInt64(verifyCrc, 0) != ddtHeader.cmpCrc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Expected DDT CRC {0:X16} but got {1:X16}", ddtHeader.cmpCrc64, + BitConverter.ToUInt64(verifyCrc, 0)); + return false; + } + + break; + case BlockType.TracksBlock: + TracksHeader trkHeader = new TracksHeader(); + structureBytes = new byte[Marshal.SizeOf(trkHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trkHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(trkHeader)); + trkHeader = (TracksHeader)Marshal.PtrToStructure(structurePointer, typeof(TracksHeader)); + Marshal.FreeHGlobal(structurePointer); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Track block at {0} contains {1} entries", header.indexOffset, + trkHeader.entries); + + structureBytes = new byte[Marshal.SizeOf(typeof(TrackEntry)) * trkHeader.entries]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + Crc64Context.Data(structureBytes, out verifyCrc); + + if(BitConverter.ToUInt64(verifyCrc, 0) != trkHeader.crc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Expected index CRC {0:X16} but got {1:X16}", trkHeader.crc64, + BitConverter.ToUInt64(verifyCrc, 0)); + return false; + } + + break; + default: + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Ignored field type {0}", + entry.blockType); + break; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscImageChef/Write.cs b/DiscImageChef.DiscImages/DiscImageChef/Write.cs new file mode 100644 index 000000000..6f724659f --- /dev/null +++ b/DiscImageChef.DiscImages/DiscImageChef/Write.cs @@ -0,0 +1,2528 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes DiscImageChef format disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using CUETools.Codecs; +using CUETools.Codecs.FLAKE; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Console; +using DiscImageChef.Decoders; +using Schemas; +using SharpCompress.Compressors.LZMA; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscImageChef + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + uint sectorsPerBlock; + uint dictionary; + uint maxDdtSize; + bool doMd5; + bool doSha1; + bool doSha256; + bool doSpamsum; + + if(options != null) + { + if(options.TryGetValue("sectors_per_block", out string tmpValue)) + { + if(!uint.TryParse(tmpValue, out sectorsPerBlock)) + { + ErrorMessage = "Invalid value for sectors_per_block option"; + return false; + } + } + else sectorsPerBlock = 4096; + + if(options.TryGetValue("dictionary", out tmpValue)) + { + if(!uint.TryParse(tmpValue, out dictionary)) + { + ErrorMessage = "Invalid value for dictionary option"; + return false; + } + } + else dictionary = 1 << 25; + + if(options.TryGetValue("max_ddt_size", out tmpValue)) + { + if(!uint.TryParse(tmpValue, out maxDdtSize)) + { + ErrorMessage = "Invalid value for max_ddt_size option"; + return false; + } + } + else maxDdtSize = 256; + + if(options.TryGetValue("md5", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out doMd5)) + { + ErrorMessage = "Invalid value for md5 option"; + return false; + } + } + else doMd5 = false; + + if(options.TryGetValue("sha1", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out doSha1)) + { + ErrorMessage = "Invalid value for sha1 option"; + return false; + } + } + else doSha1 = false; + + if(options.TryGetValue("sha256", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out doSha256)) + { + ErrorMessage = "Invalid value for sha256 option"; + return false; + } + } + else doSha256 = false; + + if(options.TryGetValue("spamsum", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out doSpamsum)) + { + ErrorMessage = "Invalid value for spamsum option"; + return false; + } + } + else doSpamsum = false; + + if(options.TryGetValue("deduplicate", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out deduplicate)) + { + ErrorMessage = "Invalid value for deduplicate option"; + return false; + } + } + else deduplicate = true; + + if(options.TryGetValue("nocompress", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out nocompress)) + { + ErrorMessage = "Invalid value for nocompress option"; + return false; + } + } + else nocompress = false; + } + else + { + sectorsPerBlock = 4096; + dictionary = 1 << 25; + maxDdtSize = 256; + doMd5 = false; + doSha1 = false; + doSha256 = false; + doSpamsum = false; + deduplicate = true; + nocompress = false; + } + + // This really, cannot happen + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + // Calculate shift + shift = 0; + uint oldSectorsPerBlock = sectorsPerBlock; + while(sectorsPerBlock > 1) + { + sectorsPerBlock >>= 1; + shift++; + } + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Got a shift of {0} for {1} sectors per block", + shift, oldSectorsPerBlock); + + imageInfo = new ImageInfo + { + MediaType = mediaType, + SectorSize = sectorSize, + Sectors = sectors, + XmlMediaType = GetXmlMediaType(mediaType) + }; + + try { imageStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + // Check if appending to an existing image + if(imageStream.Length > Marshal.SizeOf(typeof(DicHeader))) + { + header = new DicHeader(); + structureBytes = new byte[Marshal.SizeOf(header)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); + header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); + Marshal.FreeHGlobal(structurePointer); + + if(header.identifier != DIC_MAGIC) + { + ErrorMessage = "Cannot append to a non DiscImageChef format image"; + return false; + } + + if(header.imageMajorVersion > DICF_VERSION) + { + ErrorMessage = $"Cannot append to an unknown image version {header.imageMajorVersion}"; + return false; + } + + if(header.mediaType != mediaType) + { + ErrorMessage = + $"Cannot write a media with type {mediaType} to an image with type {header.mediaType}"; + return false; + } + } + else + { + header = new DicHeader + { + identifier = DIC_MAGIC, + mediaType = mediaType, + creationTime = DateTime.UtcNow.ToFileTimeUtc() + }; + + imageStream.Write(new byte[Marshal.SizeOf(typeof(DicHeader))], 0, Marshal.SizeOf(typeof(DicHeader))); + } + + header.application = "DiscImageChef"; + header.imageMajorVersion = DICF_VERSION; + header.imageMinorVersion = 0; + header.applicationMajorVersion = (byte)typeof(DiscImageChef).Assembly.GetName().Version.Major; + header.applicationMinorVersion = (byte)typeof(DiscImageChef).Assembly.GetName().Version.Minor; + + index = new List<IndexEntry>(); + + // If there exists an index, we are appending, so read index + if(header.indexOffset > 0) + { + // Can't calculate checksum of an appended image + md5Provider = null; + sha1Provider = null; + sha256Provider = null; + spamsumProvider = null; + + imageStream.Position = (long)header.indexOffset; + IndexHeader idxHeader = new IndexHeader(); + structureBytes = new byte[Marshal.SizeOf(idxHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(idxHeader)); + idxHeader = (IndexHeader)Marshal.PtrToStructure(structurePointer, typeof(IndexHeader)); + Marshal.FreeHGlobal(structurePointer); + + if(idxHeader.identifier != BlockType.Index) + { + ErrorMessage = "Index not found in existing image, cannot continue"; + return false; + } + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", + header.indexOffset, idxHeader.entries); + + for(ushort i = 0; i < idxHeader.entries; i++) + { + IndexEntry entry = new IndexEntry(); + structureBytes = new byte[Marshal.SizeOf(entry)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(entry)); + entry = (IndexEntry)Marshal.PtrToStructure(structurePointer, typeof(IndexEntry)); + Marshal.FreeHGlobal(structurePointer); + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Block type {0} with data type {1} is indexed to be at {2}", + entry.blockType, entry.dataType, entry.offset); + index.Add(entry); + } + + // Invalidate previous checksum block + index.RemoveAll(t => t.blockType == BlockType.ChecksumBlock && t.dataType == DataType.NoData); + + bool foundUserDataDdt = false; + foreach(IndexEntry entry in index) + { + imageStream.Position = (long)entry.offset; + switch(entry.blockType) + { + case BlockType.DataBlock: + switch(entry.dataType) + { + case DataType.CdSectorPrefix: + case DataType.CdSectorSuffix: + case DataType.CdSectorSubchannel: + case DataType.AppleProfileTag: + case DataType.AppleSonyTag: + case DataType.PriamDataTowerTag: break; + default: continue; + } + + BlockHeader blockHeader = new BlockHeader(); + structureBytes = new byte[Marshal.SizeOf(blockHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); + blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); + Marshal.FreeHGlobal(structurePointer); + imageInfo.ImageSize += blockHeader.cmpLength; + + if(blockHeader.identifier != entry.blockType) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect identifier for data block at position {0}", + entry.offset); + break; + } + + if(blockHeader.type != entry.dataType) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Expected block with data type {0} at position {1} but found data type {2}", + entry.dataType, entry.offset, blockHeader.type); + break; + } + + byte[] data; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found data block type {0} at position {1}", entry.dataType, + entry.offset); + + if(blockHeader.compression == CompressionType.Lzma) + { + byte[] compressedTag = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; + byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; + imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); + imageStream.Read(compressedTag, 0, compressedTag.Length); + MemoryStream compressedTagMs = new MemoryStream(compressedTag); + LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedTagMs); + data = new byte[blockHeader.length]; + lzmaBlock.Read(data, 0, (int)blockHeader.length); + lzmaBlock.Close(); + compressedTagMs.Close(); + } + else if(blockHeader.compression == CompressionType.None) + { + data = new byte[blockHeader.length]; + imageStream.Read(data, 0, (int)blockHeader.length); + } + else + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found unknown compression type {0}, continuing...", + (ushort)blockHeader.compression); + break; + } + + Crc64Context.Data(data, out byte[] blockCrc); + blockCrc = blockCrc.ToArray(); + if(BitConverter.ToUInt64(blockCrc, 0) != blockHeader.crc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", + BitConverter.ToUInt64(blockCrc, 0), blockHeader.crc64); + break; + } + + switch(entry.dataType) + { + case DataType.CdSectorPrefix: + sectorPrefix = data; + break; + case DataType.CdSectorSuffix: + sectorSuffix = data; + break; + case DataType.CdSectorSubchannel: + case DataType.AppleProfileTag: + case DataType.AppleSonyTag: + case DataType.PriamDataTowerTag: + sectorSubchannel = data; + break; + } + + break; + case BlockType.DeDuplicationTable: + // Only user data deduplication tables are used right now + if(entry.dataType != DataType.UserData) break; + + DdtHeader ddtHeader = new DdtHeader(); + structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(ddtHeader)); + ddtHeader = (DdtHeader)Marshal.PtrToStructure(structurePointer, typeof(DdtHeader)); + Marshal.FreeHGlobal(structurePointer); + + if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; + + if(ddtHeader.entries != imageInfo.Sectors) + { + ErrorMessage = + $"Trying to write a media with {imageInfo.Sectors} sectors to an image with {ddtHeader.entries} sectors, not continuing..."; + return false; + } + + shift = ddtHeader.shift; + + switch(ddtHeader.compression) + { + case CompressionType.Lzma: + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Decompressing DDT..."); + DateTime ddtStart = DateTime.UtcNow; + byte[] compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; + byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; + imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); + imageStream.Read(compressedDdt, 0, compressedDdt.Length); + MemoryStream compressedDdtMs = new MemoryStream(compressedDdt); + LzmaStream lzmaDdt = new LzmaStream(lzmaProperties, compressedDdtMs); + byte[] decompressedDdt = new byte[ddtHeader.length]; + lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); + lzmaDdt.Close(); + compressedDdtMs.Close(); + userDataDdt = new ulong[ddtHeader.entries]; + for(ulong i = 0; i < ddtHeader.entries; i++) + userDataDdt[i] = + BitConverter.ToUInt64(decompressedDdt, (int)(i * sizeof(ulong))); + DateTime ddtEnd = DateTime.UtcNow; + inMemoryDdt = true; + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Took {0} seconds to decompress DDT", + (ddtEnd - ddtStart).TotalSeconds); + break; + case CompressionType.None: + inMemoryDdt = false; + outMemoryDdtPosition = (long)entry.offset; + break; + default: + throw new + ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)ddtHeader.compression}"); + } + + foundUserDataDdt = true; + break; + // CICM XML metadata block + case BlockType.CicmBlock: + CicmMetadataBlock cicmBlock = new CicmMetadataBlock(); + structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(cicmBlock)); + cicmBlock = (CicmMetadataBlock)Marshal.PtrToStructure(structurePointer, + typeof(CicmMetadataBlock)); + Marshal.FreeHGlobal(structurePointer); + if(cicmBlock.identifier != BlockType.CicmBlock) break; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found CICM XML metadata block at position {0}", entry.offset); + + byte[] cicmBytes = new byte[cicmBlock.length]; + imageStream.Read(cicmBytes, 0, cicmBytes.Length); + MemoryStream cicmMs = new MemoryStream(cicmBytes); + XmlSerializer cicmXs = new XmlSerializer(typeof(CICMMetadataType)); + try + { + StreamReader sr = new StreamReader(cicmMs); + CicmMetadata = (CICMMetadataType)cicmXs.Deserialize(sr); + sr.Close(); + } + catch(XmlException ex) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Exception {0} processing CICM XML metadata block", + ex.Message); + CicmMetadata = null; + } + + break; + // Dump hardware block + case BlockType.DumpHardwareBlock: + DumpHardwareHeader dumpBlock = new DumpHardwareHeader(); + structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpBlock)); + dumpBlock = (DumpHardwareHeader)Marshal.PtrToStructure(structurePointer, + typeof(DumpHardwareHeader)); + Marshal.FreeHGlobal(structurePointer); + if(dumpBlock.identifier != BlockType.DumpHardwareBlock) break; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Found dump hardware block at position {0}", entry.offset); + + structureBytes = new byte[dumpBlock.length]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + Crc64Context.Data(structureBytes, out byte[] dumpCrc); + if(BitConverter.ToUInt64(dumpCrc, 0) != dumpBlock.crc64) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", + BitConverter.ToUInt64(dumpCrc, 0), dumpBlock.crc64); + break; + } + + imageStream.Position -= structureBytes.Length; + + DumpHardware = new List<DumpHardwareType>(); + + for(ushort i = 0; i < dumpBlock.entries; i++) + { + DumpHardwareEntry dumpEntry = new DumpHardwareEntry(); + structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpEntry)); + dumpEntry = (DumpHardwareEntry)Marshal.PtrToStructure(structurePointer, + typeof(DumpHardwareEntry)); + Marshal.FreeHGlobal(structurePointer); + + DumpHardwareType dump = new DumpHardwareType + { + Software = new SoftwareType(), + Extents = new ExtentType[dumpEntry.extents] + }; + + byte[] tmp; + + if(dumpEntry.manufacturerLength > 0) + { + tmp = new byte[dumpEntry.manufacturerLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Manufacturer = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.modelLength > 0) + { + tmp = new byte[dumpEntry.modelLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Model = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.revisionLength > 0) + { + tmp = new byte[dumpEntry.revisionLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Revision = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.firmwareLength > 0) + { + tmp = new byte[dumpEntry.firmwareLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Firmware = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.serialLength > 0) + { + tmp = new byte[dumpEntry.serialLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Serial = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.softwareNameLength > 0) + { + tmp = new byte[dumpEntry.softwareNameLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Software.Name = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.softwareVersionLength > 0) + { + tmp = new byte[dumpEntry.softwareVersionLength - 1]; + imageStream.Read(tmp, 0, tmp.Length); + imageStream.Position += 1; + dump.Software.Version = Encoding.UTF8.GetString(tmp); + } + + if(dumpEntry.softwareOperatingSystemLength > 0) + { + tmp = new byte[dumpEntry.softwareOperatingSystemLength - 1]; + imageStream.Position += 1; + imageStream.Read(tmp, 0, tmp.Length); + dump.Software.OperatingSystem = Encoding.UTF8.GetString(tmp); + } + + tmp = new byte[16]; + for(uint j = 0; j < dumpEntry.extents; j++) + { + imageStream.Read(tmp, 0, tmp.Length); + dump.Extents[j] = new ExtentType + { + Start = BitConverter.ToUInt64(tmp, 0), + End = BitConverter.ToUInt64(tmp, 8) + }; + } + + dump.Extents = dump.Extents.OrderBy(t => t.Start).ToArray(); + if(dump.Extents.Length > 0) DumpHardware.Add(dump); + } + + if(DumpHardware.Count == 0) DumpHardware = null; + break; + } + } + + if(!foundUserDataDdt) + { + ErrorMessage = "Could not find user data deduplication table."; + return false; + } + } + // Creating new + else + { + // Checking that DDT is smaller than requested size + inMemoryDdt = sectors <= maxDdtSize * 1024 * 1024 / sizeof(ulong); + + // If in memory, easy + if(inMemoryDdt) userDataDdt = new ulong[sectors]; + // If not, create the block, add to index, and enlarge the file to allow the DDT to exist on-disk + else + { + outMemoryDdtPosition = imageStream.Position; + index.Add(new IndexEntry + { + blockType = BlockType.DeDuplicationTable, + dataType = DataType.UserData, + offset = (ulong)outMemoryDdtPosition + }); + + // CRC64 will be calculated later + DdtHeader ddtHeader = new DdtHeader + { + identifier = BlockType.DeDuplicationTable, + type = DataType.UserData, + compression = CompressionType.None, + shift = shift, + entries = sectors, + cmpLength = sectors * sizeof(ulong), + length = sectors * sizeof(ulong) + }; + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); + structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; + Marshal.StructureToPtr(ddtHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + structureBytes = null; + + imageStream.Position += (long)(sectors * sizeof(ulong)) - 1; + imageStream.WriteByte(0); + } + + if(doMd5) md5Provider = new Md5Context(); + + if(doSha1) sha1Provider = new Sha1Context(); + + if(doSha256) sha256Provider = new Sha256Context(); + + if(doSpamsum) spamsumProvider = new SpamSumContext(); + } + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "In memory DDT?: {0}", inMemoryDdt); + + // Initialize tables + imageStream.Seek(0, SeekOrigin.End); + mediaTags = new Dictionary<MediaTagType, byte[]>(); + checksumProvider = SHA256.Create(); + deduplicationTable = new Dictionary<string, ulong>(); + trackIsrcs = new Dictionary<byte, string>(); + trackFlags = new Dictionary<byte, byte>(); + + // Initialize compressors properties (all maxed) + lzmaEncoderProperties = new LzmaEncoderProperties(true, (int)dictionary, 273); + flakeWriterSettings = new FlakeWriterSettings + { + PCM = AudioPCMConfig.RedBook, + DoMD5 = false, + BlockSize = (1 << shift) * SAMPLES_PER_SECTOR, + MinFixedOrder = 0, + MaxFixedOrder = 4, + MinLPCOrder = 1, + MaxLPCOrder = 32, + MaxPartitionOrder = 8, + StereoMethod = StereoMethod.Evaluate, + PredictionType = PredictionType.Search, + WindowMethod = WindowMethod.EvaluateN, + EstimationDepth = 5, + MinPrecisionSearch = 1, + MaxPrecisionSearch = 1, + TukeyParts = 0, + TukeyOverlap = 1.0, + TukeyP = 1.0, + AllowNonSubset = true + }; + + // Check if FLAKE's block size is bigger than what we want + if(flakeWriterSettings.BlockSize > MAX_FLAKE_BLOCK) flakeWriterSettings.BlockSize = MAX_FLAKE_BLOCK; + if(flakeWriterSettings.BlockSize < MIN_FLAKE_BLOCK) flakeWriterSettings.BlockSize = MIN_FLAKE_BLOCK; + FlakeWriter.Vendor = "DiscImageChef"; + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(mediaTags.ContainsKey(tag)) mediaTags.Remove(tag); + + mediaTags.Add(tag, data); + + ErrorMessage = ""; + return true; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress >= Info.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + if((imageInfo.XmlMediaType != XmlMediaType.OpticalDisc || !writingLong) && !rewinded) + { + if(sectorAddress <= lastWrittenBlock && alreadyWrittenZero) + { + rewinded = true; + md5Provider = null; + sha1Provider = null; + sha256Provider = null; + spamsumProvider = null; + } + + md5Provider?.Update(data); + sha1Provider?.Update(data); + sha256Provider?.Update(data); + spamsumProvider?.Update(data); + lastWrittenBlock = sectorAddress; + } + + if(sectorAddress == 0) alreadyWrittenZero = true; + + byte[] hash = null; + writtenSectors++; + + // Compute hash only if asked to deduplicate, or the sector is empty (those will always be deduplicated) + if(deduplicate || ArrayHelpers.ArrayIsNullOrEmpty(data)) hash = checksumProvider.ComputeHash(data); + string hashString = null; + + if(hash != null) + { + StringBuilder hashSb = new StringBuilder(); + foreach(byte h in hash) hashSb.Append(h.ToString("x2")); + hashString = hashSb.ToString(); + + if(deduplicationTable.TryGetValue(hashString, out ulong pointer)) + { + SetDdtEntry(sectorAddress, pointer); + ErrorMessage = ""; + return true; + } + } + + Track trk = new Track(); + + // If optical disc check track + if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) + { + trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && + sectorAddress <= t.TrackEndSector); + if(trk.TrackSequence == 0) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + "Can't found track containing requested sector"); + } + + // Close current block first + if(blockStream != null && + // When sector siz changes + (currentBlockHeader.sectorSize != data.Length || + // When block if filled + currentBlockOffset == 1 << shift || + // When we change to/from CompactDisc audio + currentBlockHeader.compression == CompressionType.Flac && trk.TrackType != TrackType.Audio)) + { + currentBlockHeader.length = currentBlockOffset * currentBlockHeader.sectorSize; + currentBlockHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0); + + Crc64Context cmpCrc64Context = new Crc64Context(); + + byte[] lzmaProperties = new byte[0]; + + if(currentBlockHeader.compression == CompressionType.Flac) + { + long remaining = currentBlockOffset * SAMPLES_PER_SECTOR % flakeWriter.Settings.BlockSize; + // Fill FLAC block + if(remaining != 0) + { + AudioBuffer audioBuffer = + new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); + flakeWriter.Write(audioBuffer); + } + + // This trick because CUETools.Codecs.Flake closes the underlying stream + long realLength = blockStream.Length; + byte[] buffer = new byte[realLength]; + flakeWriter.Close(); + Array.Copy(blockStream.GetBuffer(), 0, buffer, 0, realLength); + blockStream = new MemoryStream(buffer); + } + else if(currentBlockHeader.compression == CompressionType.Lzma) + { + lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + cmpCrc64Context.Update(lzmaProperties); + if(blockStream.Length > decompressedStream.Length) + currentBlockHeader.compression = CompressionType.None; + } + + if(currentBlockHeader.compression == CompressionType.None) + { + blockStream = decompressedStream; + currentBlockHeader.cmpCrc64 = currentBlockHeader.crc64; + } + else + { + cmpCrc64Context.Update(blockStream.ToArray()); + currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); + } + + currentBlockHeader.cmpLength = (uint)blockStream.Length; + if(currentBlockHeader.compression == CompressionType.Lzma) + currentBlockHeader.cmpLength += LZMA_PROPERTIES_LENGTH; + + index.Add(new IndexEntry + { + blockType = BlockType.DataBlock, + dataType = DataType.UserData, + offset = (ulong)imageStream.Position + }); + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(currentBlockHeader)); + structureBytes = new byte[Marshal.SizeOf(currentBlockHeader)]; + Marshal.StructureToPtr(currentBlockHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + structureBytes = null; + if(currentBlockHeader.compression == CompressionType.Lzma) + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + blockStream = null; + currentBlockOffset = 0; + } + + // No block set + if(blockStream == null) + { + currentBlockHeader = new BlockHeader + { + identifier = BlockType.DataBlock, + type = DataType.UserData, + compression = nocompress ? CompressionType.None : CompressionType.Lzma, + sectorSize = (uint)data.Length + }; + + if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc && trk.TrackType == TrackType.Audio && !nocompress + ) currentBlockHeader.compression = CompressionType.Flac; + + // JaguarCD stores data in audio tracks. FLAC is too inefficient, use LZMA there. + if(imageInfo.MediaType == MediaType.JaguarCD && trk.TrackType == TrackType.Audio && + !nocompress && + currentBlockHeader.compression == CompressionType.Flac && + trk.TrackSession > 1) currentBlockHeader.compression = CompressionType.Lzma; + + blockStream = new MemoryStream(); + decompressedStream = new MemoryStream(); + if(currentBlockHeader.compression == CompressionType.Flac) + flakeWriter = new FlakeWriter("", blockStream, flakeWriterSettings) {DoSeekTable = false}; + else lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); + crc64 = new Crc64Context(); + } + + ulong ddtEntry = (ulong)((imageStream.Position << shift) + currentBlockOffset); + if(hash != null) deduplicationTable.Add(hashString, ddtEntry); + if(currentBlockHeader.compression == CompressionType.Flac) + { + AudioBuffer audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, data, SAMPLES_PER_SECTOR); + flakeWriter.Write(audioBuffer); + } + else + { + decompressedStream.Write(data, 0, data.Length); + if(currentBlockHeader.compression == CompressionType.Lzma) lzmaBlockStream.Write(data, 0, data.Length); + } + + SetDdtEntry(sectorAddress, ddtEntry); + crc64.Update(data); + currentBlockOffset++; + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress + length > Info.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + uint sectorSize = (uint)(data.Length / length); + + for(uint i = 0; i < length; i++) + { + byte[] tmp = new byte[sectorSize]; + Array.Copy(data, i * sectorSize, tmp, 0, sectorSize); + if(!WriteSector(tmp, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + byte[] sector; + + switch(imageInfo.XmlMediaType) + { + case XmlMediaType.OpticalDisc: + Track track = + Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(data.Length != 2352) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + writingLong = true; + if(!rewinded) + { + if(sectorAddress <= lastWrittenBlock && alreadyWrittenZero) + { + rewinded = true; + md5Provider = null; + sha1Provider = null; + sha256Provider = null; + spamsumProvider = null; + } + + md5Provider?.Update(data); + sha1Provider?.Update(data); + sha256Provider?.Update(data); + spamsumProvider?.Update(data); + lastWrittenBlock = sectorAddress; + } + + // Split raw cd sector data in prefix (sync, header), user data and suffix (edc, ecc p, ecc q) + switch(track.TrackType) + { + case TrackType.Audio: + case TrackType.Data: return WriteSector(data, sectorAddress); + case TrackType.CdMode1: + if(sectorPrefix == null) sectorPrefix = new byte[imageInfo.Sectors * 16]; + if(sectorSuffix == null) sectorSuffix = new byte[imageInfo.Sectors * 288]; + sector = new byte[2048]; + Array.Copy(data, 0, sectorPrefix, (int)sectorAddress * 16, 16); + Array.Copy(data, 16, sector, 0, 2048); + Array.Copy(data, 2064, sectorSuffix, (int)sectorAddress * 288, 288); + return WriteSector(sector, sectorAddress); + case TrackType.CdMode2Formless: + case TrackType.CdMode2Form1: + case TrackType.CdMode2Form2: + if(sectorPrefix == null) sectorPrefix = new byte[imageInfo.Sectors * 16]; + if(sectorSuffix == null) sectorSuffix = new byte[imageInfo.Sectors * 288]; + sector = new byte[2336]; + Array.Copy(data, 0, sectorPrefix, (int)sectorAddress * 16, 16); + Array.Copy(data, 16, sector, 0, 2336); + return WriteSector(sector, sectorAddress); + } + + break; + case XmlMediaType.BlockMedia: + switch(imageInfo.MediaType) + { + // Split user data from Apple tags + case MediaType.AppleFileWare: + case MediaType.AppleProfile: + case MediaType.AppleSonyDS: + case MediaType.AppleSonySS: + case MediaType.AppleWidget: + case MediaType.PriamDataTower: + byte[] oldTag; + byte[] newTag; + + switch(data.Length - 512) + { + // Sony tag, convert to Profile + case 12 when imageInfo.MediaType == MediaType.AppleProfile || + imageInfo.MediaType == MediaType.AppleFileWare: + oldTag = new byte[12]; + Array.Copy(data, 512, oldTag, 0, 12); + newTag = LisaTag.DecodeSonyTag(oldTag)?.ToProfile().GetBytes(); + break; + // Sony tag, convert to Priam + case 12 when imageInfo.MediaType == MediaType.PriamDataTower: + oldTag = new byte[12]; + Array.Copy(data, 512, oldTag, 0, 12); + newTag = LisaTag.DecodeSonyTag(oldTag)?.ToPriam().GetBytes(); + break; + // Sony tag, copy to Sony + case 12 when imageInfo.MediaType == MediaType.AppleSonySS || + imageInfo.MediaType == MediaType.AppleSonySS: + newTag = new byte[12]; + Array.Copy(data, 512, newTag, 0, 12); + break; + // Profile tag, copy to Profile + case 20 when imageInfo.MediaType == MediaType.AppleProfile || + imageInfo.MediaType == MediaType.AppleFileWare: + newTag = new byte[20]; + Array.Copy(data, 512, newTag, 0, 20); + break; + // Profile tag, convert to Priam + case 20 when imageInfo.MediaType == MediaType.PriamDataTower: + oldTag = new byte[20]; + Array.Copy(data, 512, oldTag, 0, 20); + newTag = LisaTag.DecodeProfileTag(oldTag)?.ToPriam().GetBytes(); + break; + // Profile tag, convert to Sony + case 20 when imageInfo.MediaType == MediaType.AppleSonySS || + imageInfo.MediaType == MediaType.AppleSonySS: + oldTag = new byte[20]; + Array.Copy(data, 512, oldTag, 0, 20); + newTag = LisaTag.DecodeProfileTag(oldTag)?.ToSony().GetBytes(); + break; + // Priam tag, convert to Profile + case 24 when imageInfo.MediaType == MediaType.AppleProfile || + imageInfo.MediaType == MediaType.AppleFileWare: + oldTag = new byte[24]; + Array.Copy(data, 512, oldTag, 0, 24); + newTag = LisaTag.DecodePriamTag(oldTag)?.ToProfile().GetBytes(); + break; + // Priam tag, copy to Priam + case 12 when imageInfo.MediaType == MediaType.PriamDataTower: + newTag = new byte[24]; + Array.Copy(data, 512, newTag, 0, 24); + break; + // Priam tag, convert to Sony + case 24 when imageInfo.MediaType == MediaType.AppleSonySS || + imageInfo.MediaType == MediaType.AppleSonySS: + oldTag = new byte[24]; + Array.Copy(data, 512, oldTag, 0, 24); + newTag = LisaTag.DecodePriamTag(oldTag)?.ToSony().GetBytes(); + break; + case 0: + newTag = null; + break; + default: + ErrorMessage = "Incorrect data size"; + return false; + } + + sector = new byte[512]; + Array.Copy(data, 0, sector, 0, 512); + + if(newTag == null) return WriteSector(sector, sectorAddress); + + if(sectorSubchannel == null) + sectorSubchannel = new byte[newTag.Length * (int)imageInfo.Sectors]; + Array.Copy(newTag, 0, sectorSubchannel, newTag.Length * (int)sectorAddress, newTag.Length); + + return WriteSector(sector, sectorAddress); + } + + break; + } + + ErrorMessage = "Unknown long sector type, cannot write."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + byte[] sector; + switch(imageInfo.XmlMediaType) + { + case XmlMediaType.OpticalDisc: + Track track = + Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + if(data.Length % 2352 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > track.TrackEndSector + 1) + { + ErrorMessage = "Can't cross tracks"; + return false; + } + + sector = new byte[2352]; + for(uint i = 0; i < length; i++) + { + Array.Copy(data, 2352 * i, sector, 0, 2352); + if(!WriteSectorLong(sector, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + case XmlMediaType.BlockMedia: + switch(imageInfo.MediaType) + { + case MediaType.AppleFileWare: + case MediaType.AppleProfile: + case MediaType.AppleSonyDS: + case MediaType.AppleSonySS: + case MediaType.AppleWidget: + case MediaType.PriamDataTower: + int sectorSize = 0; + if(data.Length % 524 == 0) sectorSize = 524; + else if(data.Length % 532 == 0) sectorSize = 532; + else if(data.Length % 536 == 0) sectorSize = 536; + + if(sectorSize == 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + sector = new byte[sectorSize]; + for(uint i = 0; i < length; i++) + { + Array.Copy(data, sectorSize * i, sector, 0, sectorSize); + if(!WriteSectorLong(sector, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + } + + break; + } + + ErrorMessage = "Unknown long sector type, cannot write."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + Tracks = tracks; + ErrorMessage = ""; + return true; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + // Close current block first + if(blockStream != null) + { + currentBlockHeader.length = currentBlockOffset * currentBlockHeader.sectorSize; + currentBlockHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0); + + Crc64Context cmpCrc64Context = new Crc64Context(); + + byte[] lzmaProperties = new byte[0]; + + if(currentBlockHeader.compression == CompressionType.Flac) + { + long remaining = currentBlockOffset * SAMPLES_PER_SECTOR % flakeWriter.Settings.BlockSize; + // Fill FLAC block + if(remaining != 0) + { + AudioBuffer audioBuffer = + new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); + flakeWriter.Write(audioBuffer); + } + + // This trick because CUETools.Codecs.Flake closes the underlying stream + long realLength = blockStream.Length; + byte[] buffer = new byte[realLength]; + flakeWriter.Close(); + Array.Copy(blockStream.GetBuffer(), 0, buffer, 0, realLength); + blockStream = new MemoryStream(buffer); + } + else if(currentBlockHeader.compression == CompressionType.Lzma) + { + lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + cmpCrc64Context.Update(lzmaProperties); + if(blockStream.Length > decompressedStream.Length) + currentBlockHeader.compression = CompressionType.None; + } + + if(currentBlockHeader.compression == CompressionType.None) + { + blockStream = decompressedStream; + currentBlockHeader.cmpCrc64 = currentBlockHeader.crc64; + } + else + { + cmpCrc64Context.Update(blockStream.ToArray()); + currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); + } + + currentBlockHeader.cmpLength = (uint)blockStream.Length; + if(currentBlockHeader.compression == CompressionType.Lzma) + currentBlockHeader.cmpLength += LZMA_PROPERTIES_LENGTH; + + index.Add(new IndexEntry + { + blockType = BlockType.DataBlock, + dataType = DataType.UserData, + offset = (ulong)imageStream.Position + }); + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(currentBlockHeader)); + structureBytes = new byte[Marshal.SizeOf(currentBlockHeader)]; + Marshal.StructureToPtr(currentBlockHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + structureBytes = null; + if(currentBlockHeader.compression == CompressionType.Lzma) + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + } + + if(deduplicate) + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Of {0} sectors written, {1} are unique ({2:P})", writtenSectors, + deduplicationTable.Count, (double)deduplicationTable.Count / writtenSectors); + + IndexEntry idxEntry; + + // Write media tag blocks + foreach(KeyValuePair<MediaTagType, byte[]> mediaTag in mediaTags) + { + DataType dataType = GetDataTypeForMediaTag(mediaTag.Key); + idxEntry = new IndexEntry + { + blockType = BlockType.DataBlock, + dataType = dataType, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tag type {0} to position {1}", + mediaTag.Key, idxEntry.offset); + + Crc64Context.Data(mediaTag.Value, out byte[] tagCrc); + + BlockHeader tagBlock = new BlockHeader + { + identifier = BlockType.DataBlock, + type = dataType, + length = (uint)mediaTag.Value.Length, + crc64 = BitConverter.ToUInt64(tagCrc, 0) + }; + + blockStream = new MemoryStream(); + lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); + lzmaBlockStream.Write(mediaTag.Value, 0, mediaTag.Value.Length); + byte[] lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + byte[] tagData; + + // Not compressible + if(blockStream.Length + LZMA_PROPERTIES_LENGTH >= mediaTag.Value.Length) + { + tagBlock.cmpLength = tagBlock.length; + tagBlock.cmpCrc64 = tagBlock.crc64; + tagData = mediaTag.Value; + tagBlock.compression = CompressionType.None; + } + else + { + tagData = blockStream.ToArray(); + Crc64Context crc64Ctx = new Crc64Context(); + crc64Ctx.Update(lzmaProperties); + crc64Ctx.Update(tagData); + tagCrc = crc64Ctx.Final(); + tagBlock.cmpLength = (uint)tagData.Length + LZMA_PROPERTIES_LENGTH; + tagBlock.cmpCrc64 = BitConverter.ToUInt64(tagCrc, 0); + tagBlock.compression = CompressionType.Lzma; + } + + lzmaBlockStream = null; + blockStream = null; + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(tagBlock)); + structureBytes = new byte[Marshal.SizeOf(tagBlock)]; + Marshal.StructureToPtr(tagBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + if(tagBlock.compression == CompressionType.Lzma) + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + imageStream.Write(tagData, 0, tagData.Length); + + index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == dataType); + + index.Add(idxEntry); + } + + // If we have set the geometry block, write it + if(geometryBlock.identifier == BlockType.GeometryBlock) + { + idxEntry = new IndexEntry + { + blockType = BlockType.GeometryBlock, + dataType = DataType.NoData, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing geometry block to position {0}", + idxEntry.offset); + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(geometryBlock)); + structureBytes = new byte[Marshal.SizeOf(geometryBlock)]; + Marshal.StructureToPtr(geometryBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + + index.RemoveAll(t => t.blockType == BlockType.GeometryBlock && t.dataType == DataType.NoData); + + index.Add(idxEntry); + } + + // If we have dump hardware, write it + if(DumpHardware != null) + { + MemoryStream dumpMs = new MemoryStream(); + foreach(DumpHardwareType dump in DumpHardware) + { + byte[] dumpManufacturer = null; + byte[] dumpModel = null; + byte[] dumpRevision = null; + byte[] dumpFirmware = null; + byte[] dumpSerial = null; + byte[] dumpSoftwareName = null; + byte[] dumpSoftwareVersion = null; + byte[] dumpSoftwareOperatingSystem = null; + + if(!string.IsNullOrWhiteSpace(dump.Manufacturer)) + dumpManufacturer = Encoding.UTF8.GetBytes(dump.Manufacturer); + if(!string.IsNullOrWhiteSpace(dump.Model)) dumpModel = Encoding.UTF8.GetBytes(dump.Model); + if(!string.IsNullOrWhiteSpace(dump.Revision)) dumpRevision = Encoding.UTF8.GetBytes(dump.Revision); + if(!string.IsNullOrWhiteSpace(dump.Firmware)) dumpFirmware = Encoding.UTF8.GetBytes(dump.Firmware); + if(!string.IsNullOrWhiteSpace(dump.Serial)) dumpSerial = Encoding.UTF8.GetBytes(dump.Serial); + if(dump.Software != null) + { + if(!string.IsNullOrWhiteSpace(dump.Software.Name)) + dumpSoftwareName = Encoding.UTF8.GetBytes(dump.Software.Name); + if(!string.IsNullOrWhiteSpace(dump.Software.Version)) + dumpSoftwareVersion = Encoding.UTF8.GetBytes(dump.Software.Version); + if(!string.IsNullOrWhiteSpace(dump.Software.OperatingSystem)) + dumpSoftwareOperatingSystem = Encoding.UTF8.GetBytes(dump.Software.OperatingSystem); + } + + DumpHardwareEntry dumpEntry = new DumpHardwareEntry + { + manufacturerLength = (uint)(dumpManufacturer?.Length + 1 ?? 0), + modelLength = (uint)(dumpModel?.Length + 1 ?? 0), + revisionLength = (uint)(dumpRevision?.Length + 1 ?? 0), + firmwareLength = (uint)(dumpFirmware?.Length + 1 ?? 0), + serialLength = (uint)(dumpSerial?.Length + 1 ?? 0), + softwareNameLength = (uint)(dumpSoftwareName?.Length + 1 ?? 0), + softwareVersionLength = (uint)(dumpSoftwareVersion?.Length + 1 ?? 0), + softwareOperatingSystemLength = (uint)(dumpSoftwareOperatingSystem?.Length + 1 ?? 0), + extents = (uint)dump.Extents.Length + }; + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); + structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; + Marshal.StructureToPtr(dumpEntry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + dumpMs.Write(structureBytes, 0, structureBytes.Length); + + if(dumpManufacturer != null) + { + dumpMs.Write(dumpManufacturer, 0, dumpManufacturer.Length); + dumpMs.WriteByte(0); + } + + if(dumpModel != null) + { + dumpMs.Write(dumpModel, 0, dumpModel.Length); + dumpMs.WriteByte(0); + } + + if(dumpRevision != null) + { + dumpMs.Write(dumpRevision, 0, dumpRevision.Length); + dumpMs.WriteByte(0); + } + + if(dumpFirmware != null) + { + dumpMs.Write(dumpFirmware, 0, dumpFirmware.Length); + dumpMs.WriteByte(0); + } + + if(dumpSerial != null) + { + dumpMs.Write(dumpSerial, 0, dumpSerial.Length); + dumpMs.WriteByte(0); + } + + if(dumpSoftwareName != null) + { + dumpMs.Write(dumpSoftwareName, 0, dumpSoftwareName.Length); + dumpMs.WriteByte(0); + } + + if(dumpSoftwareVersion != null) + { + dumpMs.Write(dumpSoftwareVersion, 0, dumpSoftwareVersion.Length); + dumpMs.WriteByte(0); + } + + if(dumpSoftwareOperatingSystem != null) + { + dumpMs.Write(dumpSoftwareOperatingSystem, 0, dumpSoftwareOperatingSystem.Length); + dumpMs.WriteByte(0); + } + + foreach(ExtentType extent in dump.Extents) + { + dumpMs.Write(BitConverter.GetBytes(extent.Start), 0, sizeof(ulong)); + dumpMs.Write(BitConverter.GetBytes(extent.End), 0, sizeof(ulong)); + } + } + + idxEntry = new IndexEntry + { + blockType = BlockType.DumpHardwareBlock, + dataType = DataType.NoData, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing dump hardware block to position {0}", + idxEntry.offset); + + Crc64Context.Data(dumpMs.ToArray(), out byte[] dumpCrc); + DumpHardwareHeader dumpBlock = new DumpHardwareHeader + { + identifier = BlockType.DumpHardwareBlock, + entries = (ushort)DumpHardware.Count, + crc64 = BitConverter.ToUInt64(dumpCrc, 0), + length = (uint)dumpMs.Length + }; + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); + structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; + Marshal.StructureToPtr(dumpBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + imageStream.Write(dumpMs.ToArray(), 0, (int)dumpMs.Length); + + index.RemoveAll(t => t.blockType == BlockType.DumpHardwareBlock && t.dataType == DataType.NoData); + + index.Add(idxEntry); + } + + // If we have CICM XML metadata, write it + if(CicmMetadata != null) + { + MemoryStream cicmMs = new MemoryStream(); + XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType)); + xmlSer.Serialize(cicmMs, CicmMetadata); + + idxEntry = new IndexEntry + { + blockType = BlockType.CicmBlock, + dataType = DataType.NoData, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CICM XML block to position {0}", + idxEntry.offset); + + CicmMetadataBlock cicmBlock = + new CicmMetadataBlock {identifier = BlockType.CicmBlock, length = (uint)cicmMs.Length}; + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); + structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; + Marshal.StructureToPtr(cicmBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + imageStream.Write(cicmMs.ToArray(), 0, (int)cicmMs.Length); + + index.RemoveAll(t => t.blockType == BlockType.CicmBlock && t.dataType == DataType.NoData); + + index.Add(idxEntry); + } + + // If we have checksums, write it to disk + if(md5Provider != null || sha1Provider != null || sha256Provider != null || spamsumProvider != null) + { + MemoryStream chkMs = new MemoryStream(); + ChecksumHeader chkHeader = new ChecksumHeader {identifier = BlockType.ChecksumBlock}; + + if(md5Provider != null) + { + byte[] md5 = md5Provider.Final(); + ChecksumEntry md5Entry = + new ChecksumEntry {type = ChecksumAlgorithm.Md5, length = (uint)md5.Length}; + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(md5Entry)); + structureBytes = new byte[Marshal.SizeOf(md5Entry)]; + Marshal.StructureToPtr(md5Entry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + chkMs.Write(structureBytes, 0, structureBytes.Length); + chkMs.Write(md5, 0, md5.Length); + chkHeader.entries++; + } + + if(sha1Provider != null) + { + byte[] sha1 = sha1Provider.Final(); + ChecksumEntry sha1Entry = + new ChecksumEntry {type = ChecksumAlgorithm.Sha1, length = (uint)sha1.Length}; + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(sha1Entry)); + structureBytes = new byte[Marshal.SizeOf(sha1Entry)]; + Marshal.StructureToPtr(sha1Entry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + chkMs.Write(structureBytes, 0, structureBytes.Length); + chkMs.Write(sha1, 0, sha1.Length); + chkHeader.entries++; + } + + if(sha256Provider != null) + { + byte[] sha256 = sha256Provider.Final(); + ChecksumEntry sha256Entry = + new ChecksumEntry {type = ChecksumAlgorithm.Sha256, length = (uint)sha256.Length}; + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(sha256Entry)); + structureBytes = new byte[Marshal.SizeOf(sha256Entry)]; + Marshal.StructureToPtr(sha256Entry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + chkMs.Write(structureBytes, 0, structureBytes.Length); + chkMs.Write(sha256, 0, sha256.Length); + chkHeader.entries++; + } + + if(spamsumProvider != null) + { + byte[] spamsum = Encoding.ASCII.GetBytes(spamsumProvider.End()); + ChecksumEntry spamsumEntry = + new ChecksumEntry {type = ChecksumAlgorithm.SpamSum, length = (uint)spamsum.Length}; + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(spamsumEntry)); + structureBytes = new byte[Marshal.SizeOf(spamsumEntry)]; + Marshal.StructureToPtr(spamsumEntry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + chkMs.Write(structureBytes, 0, structureBytes.Length); + chkMs.Write(spamsum, 0, spamsum.Length); + chkHeader.entries++; + } + + if(chkHeader.entries > 0) + { + chkHeader.length = (uint)chkMs.Length; + idxEntry = new IndexEntry + { + blockType = BlockType.ChecksumBlock, + dataType = DataType.NoData, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing checksum block to position {0}", + idxEntry.offset); + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(chkHeader)); + structureBytes = new byte[Marshal.SizeOf(chkHeader)]; + Marshal.StructureToPtr(chkHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + imageStream.Write(chkMs.ToArray(), 0, (int)chkMs.Length); + + index.RemoveAll(t => t.blockType == BlockType.ChecksumBlock && t.dataType == DataType.NoData); + + index.Add(idxEntry); + } + } + + // If the DDT is in-memory, write it to disk + if(inMemoryDdt) + { + idxEntry = new IndexEntry + { + blockType = BlockType.DeDuplicationTable, + dataType = DataType.UserData, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing user data DDT to position {0}", + idxEntry.offset); + + DdtHeader ddtHeader = new DdtHeader + { + identifier = BlockType.DeDuplicationTable, + type = DataType.UserData, + compression = CompressionType.Lzma, + shift = shift, + entries = (ulong)userDataDdt.LongLength, + length = (ulong)(userDataDdt.LongLength * sizeof(ulong)) + }; + + blockStream = new MemoryStream(); + lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); + crc64 = new Crc64Context(); + for(ulong i = 0; i < (ulong)userDataDdt.LongLength; i++) + { + byte[] ddtEntry = BitConverter.GetBytes(userDataDdt[i]); + crc64.Update(ddtEntry); + lzmaBlockStream.Write(ddtEntry, 0, ddtEntry.Length); + } + + byte[] lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + ddtHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; + Crc64Context cmpCrc64Context = new Crc64Context(); + cmpCrc64Context.Update(lzmaProperties); + cmpCrc64Context.Update(blockStream.ToArray()); + ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); + structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; + Marshal.StructureToPtr(ddtHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + structureBytes = null; + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + blockStream = null; + lzmaBlockStream = null; + + index.RemoveAll(t => t.blockType == BlockType.DeDuplicationTable && t.dataType == DataType.UserData); + + index.Add(idxEntry); + } + + // Write the sector prefix, suffix and subchannels if present + switch(imageInfo.XmlMediaType) + { + case XmlMediaType.OpticalDisc when Tracks != null && Tracks.Count > 0: + DateTime startCompress; + DateTime endCompress; + if(sectorPrefix != null && sectorSuffix != null) + { + idxEntry = new IndexEntry + { + blockType = BlockType.DataBlock, + dataType = DataType.CdSectorPrefix, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Writing CD sector prefix block to position {0}", idxEntry.offset); + + Crc64Context.Data(sectorPrefix, out byte[] blockCrc); + + BlockHeader prefixBlock = new BlockHeader + { + identifier = BlockType.DataBlock, + type = DataType.CdSectorPrefix, + length = (uint)sectorPrefix.Length, + crc64 = BitConverter.ToUInt64(blockCrc, 0), + sectorSize = 16 + }; + + byte[] lzmaProperties = null; + + if(nocompress) + { + prefixBlock.compression = CompressionType.None; + prefixBlock.cmpCrc64 = prefixBlock.crc64; + prefixBlock.cmpLength = prefixBlock.length; + blockStream = new MemoryStream(sectorPrefix); + } + else + { + startCompress = DateTime.Now; + blockStream = new MemoryStream(); + lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); + lzmaBlockStream.Write(sectorPrefix, 0, sectorPrefix.Length); + lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + + Crc64Context cmpCrc = new Crc64Context(); + cmpCrc.Update(lzmaProperties); + cmpCrc.Update(blockStream.ToArray()); + blockCrc = cmpCrc.Final(); + prefixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; + prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); + prefixBlock.compression = CompressionType.Lzma; + + lzmaBlockStream = null; + endCompress = DateTime.Now; + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Took {0} seconds to compress prefix", + (endCompress - startCompress).TotalSeconds); + } + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(prefixBlock)); + structureBytes = new byte[Marshal.SizeOf(prefixBlock)]; + Marshal.StructureToPtr(prefixBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + if(prefixBlock.compression == CompressionType.Lzma) + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + + index.RemoveAll(t => t.blockType == BlockType.DataBlock && + t.dataType == DataType.CdSectorPrefix); + + index.Add(idxEntry); + + idxEntry = new IndexEntry + { + blockType = BlockType.DataBlock, + dataType = DataType.CdSectorSuffix, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Writing CD sector suffix block to position {0}", idxEntry.offset); + + Crc64Context.Data(sectorSuffix, out blockCrc); + + prefixBlock = new BlockHeader + { + identifier = BlockType.DataBlock, + type = DataType.CdSectorSuffix, + length = (uint)sectorSuffix.Length, + crc64 = BitConverter.ToUInt64(blockCrc, 0), + sectorSize = 288 + }; + + if(nocompress) + { + prefixBlock.compression = CompressionType.None; + prefixBlock.cmpCrc64 = prefixBlock.crc64; + prefixBlock.cmpLength = prefixBlock.length; + blockStream = new MemoryStream(sectorSuffix); + } + else + { + startCompress = DateTime.Now; + blockStream = new MemoryStream(); + lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); + lzmaBlockStream.Write(sectorSuffix, 0, sectorSuffix.Length); + lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + + Crc64Context cmpCrc = new Crc64Context(); + cmpCrc.Update(lzmaProperties); + cmpCrc.Update(blockStream.ToArray()); + blockCrc = cmpCrc.Final(); + prefixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; + prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); + prefixBlock.compression = CompressionType.Lzma; + + lzmaBlockStream = null; + endCompress = DateTime.Now; + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Took {0} seconds to compress suffix", + (endCompress - startCompress).TotalSeconds); + } + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(prefixBlock)); + structureBytes = new byte[Marshal.SizeOf(prefixBlock)]; + Marshal.StructureToPtr(prefixBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + if(prefixBlock.compression == CompressionType.Lzma) + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + + index.RemoveAll(t => t.blockType == BlockType.DataBlock && + t.dataType == DataType.CdSectorSuffix); + + index.Add(idxEntry); + blockStream = null; + } + + if(sectorSubchannel != null) + { + idxEntry = new IndexEntry + { + blockType = BlockType.DataBlock, + dataType = DataType.CdSectorSubchannel, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Writing CD subchannel block to position {0}", idxEntry.offset); + + Crc64Context.Data(sectorSubchannel, out byte[] blockCrc); + + BlockHeader subchannelBlock = new BlockHeader + { + identifier = BlockType.DataBlock, + type = DataType.CdSectorSubchannel, + length = (uint)sectorSubchannel.Length, + crc64 = BitConverter.ToUInt64(blockCrc, 0), + sectorSize = 96 + }; + + byte[] lzmaProperties = null; + + if(nocompress) + { + subchannelBlock.compression = CompressionType.None; + subchannelBlock.cmpCrc64 = subchannelBlock.crc64; + subchannelBlock.cmpLength = subchannelBlock.length; + blockStream = new MemoryStream(sectorSubchannel); + } + else + { + startCompress = DateTime.Now; + byte[] transformedSubchannel = ClauniaSubchannelTransform(sectorSubchannel); + blockStream = new MemoryStream(); + lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); + lzmaBlockStream.Write(transformedSubchannel, 0, transformedSubchannel.Length); + lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + + Crc64Context cmpCrc = new Crc64Context(); + cmpCrc.Update(lzmaProperties); + cmpCrc.Update(blockStream.ToArray()); + blockCrc = cmpCrc.Final(); + subchannelBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; + subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); + subchannelBlock.compression = CompressionType.LzmaClauniaSubchannelTransform; + + lzmaBlockStream = null; + endCompress = DateTime.Now; + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Took {0} seconds to compress subchannel", + (endCompress - startCompress).TotalSeconds); + } + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(subchannelBlock)); + structureBytes = new byte[Marshal.SizeOf(subchannelBlock)]; + Marshal.StructureToPtr(subchannelBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + if(subchannelBlock.compression == CompressionType.Lzma || subchannelBlock.compression == + CompressionType.LzmaClauniaSubchannelTransform) + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + + index.RemoveAll(t => t.blockType == BlockType.DataBlock && + t.dataType == DataType.CdSectorSubchannel); + + index.Add(idxEntry); + blockStream = null; + } + + List<TrackEntry> trackEntries = new List<TrackEntry>(); + foreach(Track track in Tracks) + { + trackFlags.TryGetValue((byte)track.TrackSequence, out byte flags); + trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc); + + if((flags & (int)CdFlags.DataTrack) == 0 && track.TrackType != TrackType.Audio) + flags += (byte)CdFlags.DataTrack; + + trackEntries.Add(new TrackEntry + { + sequence = (byte)track.TrackSequence, + type = track.TrackType, + start = (long)track.TrackStartSector, + end = (long)track.TrackEndSector, + pregap = (long)track.TrackPregap, + session = (byte)track.TrackSession, + isrc = isrc, + flags = flags + }); + } + + // If there are tracks build the tracks block + if(trackEntries.Count > 0) + { + blockStream = new MemoryStream(); + + foreach(TrackEntry entry in trackEntries) + { + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); + structureBytes = new byte[Marshal.SizeOf(entry)]; + Marshal.StructureToPtr(entry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + blockStream.Write(structureBytes, 0, structureBytes.Length); + } + + Crc64Context.Data(blockStream.ToArray(), out byte[] trksCrc); + TracksHeader trkHeader = new TracksHeader + { + identifier = BlockType.TracksBlock, + entries = (ushort)trackEntries.Count, + crc64 = BitConverter.ToUInt64(trksCrc, 0) + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tracks to position {0}", + imageStream.Position); + + index.RemoveAll(t => t.blockType == BlockType.TracksBlock && t.dataType == DataType.NoData); + + index.Add(new IndexEntry + { + blockType = BlockType.TracksBlock, + dataType = DataType.NoData, + offset = (ulong)imageStream.Position + }); + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trkHeader)); + structureBytes = new byte[Marshal.SizeOf(trkHeader)]; + Marshal.StructureToPtr(trkHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + } + + break; + case XmlMediaType.BlockMedia: + if(sectorSubchannel != null && (imageInfo.MediaType == MediaType.AppleFileWare || + imageInfo.MediaType == MediaType.AppleSonySS || + imageInfo.MediaType == MediaType.AppleSonyDS || + imageInfo.MediaType == MediaType.AppleProfile || + imageInfo.MediaType == MediaType.AppleWidget || + imageInfo.MediaType == MediaType.PriamDataTower)) + { + DataType tagType = DataType.NoData; + + switch(imageInfo.MediaType) + { + case MediaType.AppleSonySS: + case MediaType.AppleSonyDS: + tagType = DataType.AppleSonyTag; + break; + case MediaType.AppleFileWare: + case MediaType.AppleProfile: + case MediaType.AppleWidget: + tagType = DataType.AppleProfileTag; + break; + case MediaType.PriamDataTower: + tagType = DataType.PriamDataTowerTag; + break; + } + + idxEntry = new IndexEntry + { + blockType = BlockType.DataBlock, + dataType = tagType, + offset = (ulong)imageStream.Position + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Writing apple sector tag block to position {0}", idxEntry.offset); + + Crc64Context.Data(sectorSubchannel, out byte[] blockCrc); + + BlockHeader subchannelBlock = new BlockHeader + { + identifier = BlockType.DataBlock, + type = tagType, + length = (uint)sectorSubchannel.Length, + crc64 = BitConverter.ToUInt64(blockCrc, 0) + }; + + switch(imageInfo.MediaType) + { + case MediaType.AppleSonySS: + case MediaType.AppleSonyDS: + subchannelBlock.sectorSize = 12; + break; + case MediaType.AppleFileWare: + case MediaType.AppleProfile: + case MediaType.AppleWidget: + subchannelBlock.sectorSize = 20; + break; + case MediaType.PriamDataTower: + subchannelBlock.sectorSize = 24; + break; + } + + byte[] lzmaProperties = null; + + if(nocompress) + { + subchannelBlock.compression = CompressionType.None; + subchannelBlock.cmpCrc64 = subchannelBlock.crc64; + subchannelBlock.cmpLength = subchannelBlock.length; + blockStream = new MemoryStream(sectorSubchannel); + } + else + { + blockStream = new MemoryStream(); + lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); + lzmaBlockStream.Write(sectorSubchannel, 0, sectorSubchannel.Length); + lzmaProperties = lzmaBlockStream.Properties; + lzmaBlockStream.Close(); + + Crc64Context cmpCrc = new Crc64Context(); + cmpCrc.Update(lzmaProperties); + cmpCrc.Update(blockStream.ToArray()); + blockCrc = cmpCrc.Final(); + subchannelBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; + subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); + subchannelBlock.compression = CompressionType.Lzma; + + lzmaBlockStream = null; + } + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(subchannelBlock)); + structureBytes = new byte[Marshal.SizeOf(subchannelBlock)]; + Marshal.StructureToPtr(subchannelBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + if(subchannelBlock.compression == CompressionType.Lzma) + imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + + index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == tagType); + + index.Add(idxEntry); + blockStream = null; + } + + break; + } + + // Write metadata if present + SetMetadataFromTags(); + MetadataBlock metadataBlock = new MetadataBlock(); + blockStream = new MemoryStream(); + blockStream.Write(new byte[Marshal.SizeOf(metadataBlock)], 0, Marshal.SizeOf(metadataBlock)); + byte[] tmpUtf16Le; + + if(imageInfo.MediaSequence > 0 && imageInfo.LastMediaSequence > 0) + { + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.mediaSequence = imageInfo.MediaSequence; + metadataBlock.lastMediaSequence = imageInfo.LastMediaSequence; + } + + if(!string.IsNullOrWhiteSpace(imageInfo.Creator)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.Creator); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.creatorOffset = (uint)blockStream.Position; + metadataBlock.creatorLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.Comments)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.Comments); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.commentsOffset = (uint)blockStream.Position; + metadataBlock.commentsLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.MediaTitle)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaTitle); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.mediaTitleOffset = (uint)blockStream.Position; + metadataBlock.mediaTitleLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.MediaManufacturer)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaManufacturer); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.mediaManufacturerOffset = (uint)blockStream.Position; + metadataBlock.mediaManufacturerLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.MediaModel)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaModel); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.mediaModelOffset = (uint)blockStream.Position; + metadataBlock.mediaModelLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.MediaSerialNumber)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaSerialNumber); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.mediaSerialNumberOffset = (uint)blockStream.Position; + metadataBlock.mediaSerialNumberLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.MediaBarcode)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaBarcode); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.mediaBarcodeOffset = (uint)blockStream.Position; + metadataBlock.mediaBarcodeLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.MediaPartNumber)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaPartNumber); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.mediaPartNumberOffset = (uint)blockStream.Position; + metadataBlock.mediaPartNumberLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveManufacturer); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.driveManufacturerOffset = (uint)blockStream.Position; + metadataBlock.driveManufacturerLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.DriveModel)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveModel); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.driveModelOffset = (uint)blockStream.Position; + metadataBlock.driveModelLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveSerialNumber); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.driveSerialNumberOffset = (uint)blockStream.Position; + metadataBlock.driveSerialNumberLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + if(!string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) + { + tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveFirmwareRevision); + metadataBlock.identifier = BlockType.MetadataBlock; + metadataBlock.driveFirmwareRevisionOffset = (uint)blockStream.Position; + metadataBlock.driveFirmwareRevisionLength = (uint)(tmpUtf16Le.Length + 2); + blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); + blockStream.Write(new byte[] {0, 0}, 0, 2); + } + + // Check if we set up any metadata earlier, then write its block + if(metadataBlock.identifier == BlockType.MetadataBlock) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing metadata to position {0}", + imageStream.Position); + metadataBlock.blockSize = (uint)blockStream.Length; + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(metadataBlock)); + structureBytes = new byte[Marshal.SizeOf(metadataBlock)]; + Marshal.StructureToPtr(metadataBlock, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + blockStream.Position = 0; + blockStream.Write(structureBytes, 0, structureBytes.Length); + index.RemoveAll(t => t.blockType == BlockType.MetadataBlock && t.dataType == DataType.NoData); + + index.Add(new IndexEntry + { + blockType = BlockType.MetadataBlock, + dataType = DataType.NoData, + offset = (ulong)imageStream.Position + }); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + } + + header.indexOffset = (ulong)imageStream.Position; + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing index to position {0}", + header.indexOffset); + + blockStream = new MemoryStream(); + + // Write index to memory + foreach(IndexEntry entry in index) + { + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); + structureBytes = new byte[Marshal.SizeOf(entry)]; + Marshal.StructureToPtr(entry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + blockStream.Write(structureBytes, 0, structureBytes.Length); + } + + Crc64Context.Data(blockStream.ToArray(), out byte[] idxCrc); + + IndexHeader idxHeader = new IndexHeader + { + identifier = BlockType.Index, + entries = (ushort)index.Count, + crc64 = BitConverter.ToUInt64(idxCrc, 0) + }; + + // Write index to disk + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); + structureBytes = new byte[Marshal.SizeOf(idxHeader)]; + Marshal.StructureToPtr(idxHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing header"); + header.lastWrittenTime = DateTime.UtcNow.ToFileTimeUtc(); + imageStream.Position = 0; + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + structureBytes = new byte[Marshal.SizeOf(header)]; + Marshal.StructureToPtr(header, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + + imageStream.Flush(); + imageStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.Creator = metadata.Creator; + imageInfo.Comments = metadata.Comments; + imageInfo.MediaManufacturer = metadata.MediaManufacturer; + imageInfo.MediaModel = metadata.MediaModel; + imageInfo.MediaSerialNumber = metadata.MediaSerialNumber; + imageInfo.MediaBarcode = metadata.MediaBarcode; + imageInfo.MediaPartNumber = metadata.MediaPartNumber; + imageInfo.MediaSequence = metadata.MediaSequence; + imageInfo.LastMediaSequence = metadata.LastMediaSequence; + imageInfo.DriveManufacturer = metadata.DriveManufacturer; + imageInfo.DriveModel = metadata.DriveModel; + imageInfo.DriveSerialNumber = metadata.DriveSerialNumber; + imageInfo.DriveFirmwareRevision = metadata.DriveFirmwareRevision; + imageInfo.MediaTitle = metadata.MediaTitle; + + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(imageInfo.XmlMediaType != XmlMediaType.BlockMedia) + { + ErrorMessage = "Tried to set geometry on a media that doesn't suppport it"; + return false; + } + + geometryBlock = new GeometryBlock + { + identifier = BlockType.GeometryBlock, + cylinders = cylinders, + heads = heads, + sectorsPerTrack = sectorsPerTrack + }; + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + Track track = new Track(); + switch(tag) + { + case SectorTagType.CdTrackFlags: + case SectorTagType.CdTrackIsrc: + case SectorTagType.CdSectorSubchannel: + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + { + ErrorMessage = "Incorrect tag for disk type"; + return false; + } + + track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + break; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + { + if(data.Length != 1) + { + ErrorMessage = "Incorrect data size for track flags"; + return false; + } + + trackFlags.Add((byte)track.TrackSequence, data[0]); + + return true; + } + case SectorTagType.CdTrackIsrc: + { + if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); + return true; + } + case SectorTagType.CdSectorSubchannel: + { + if(data.Length != 96) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + if(sectorSubchannel == null) sectorSubchannel = new byte[imageInfo.Sectors * 96]; + + Array.Copy(data, 0, sectorSubchannel, (int)(96 * sectorAddress), 96); + + return true; + } + default: + ErrorMessage = $"Don't know how to write sector tag type {tag}"; + return false; + } + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); + case SectorTagType.CdSectorSubchannel: + { + if(data.Length % 96 != 0) + { + ErrorMessage = "Incorrect data size for subchannel"; + return false; + } + + if(sectorSubchannel == null) sectorSubchannel = new byte[imageInfo.Sectors * 96]; + + if(sectorAddress * 96 + length * 96 > (ulong)sectorSubchannel.LongLength) + { + ErrorMessage = "Tried to write more data than possible"; + return false; + } + + Array.Copy(data, 0, sectorSubchannel, (int)(96 * sectorAddress), 96 * length); + + return true; + } + + default: + ErrorMessage = $"Don't know how to write sector tag type {tag}"; + return false; + } + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + DumpHardware = dumpHardware; + return true; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + CicmMetadata = metadata; + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscJuggler/DiscJuggler.cs b/DiscImageChef.DiscImages/DiscJuggler/DiscJuggler.cs new file mode 100644 index 000000000..149b46f59 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscJuggler/DiscJuggler.cs @@ -0,0 +1,89 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : DiscJuggler.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages DiscJuggler disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Console; +using Schemas; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + // Support separate data files? Never seen a DiscJuggler image using them anyways... + // TODO: Too many unknowns to make this writable + public partial class DiscJuggler : IMediaImage + { + byte[] cdtext; + ImageInfo imageInfo; + Stream imageStream; + Dictionary<uint, ulong> offsetmap; + Dictionary<uint, byte> trackFlags; + + public DiscJuggler() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = true, + HasSessions = true, + Version = null, + ApplicationVersion = null, + MediaTitle = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + + + + + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscJuggler/Helpers.cs b/DiscImageChef.DiscImages/DiscJuggler/Helpers.cs new file mode 100644 index 000000000..c262488ae --- /dev/null +++ b/DiscImageChef.DiscImages/DiscJuggler/Helpers.cs @@ -0,0 +1,50 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for DiscJuggler disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscJuggler + { + static MediaType DecodeCdiMediumType(ushort type) + { + switch(type) + { + case 56: return MediaType.DVDROM; + case 152: return MediaType.CDROM; + default: return MediaType.Unknown; + } + } + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscJuggler/Identify.cs b/DiscImageChef.DiscImages/DiscJuggler/Identify.cs new file mode 100644 index 000000000..173b51598 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscJuggler/Identify.cs @@ -0,0 +1,73 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies DiscJuggler disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscJuggler + { + public bool Identify(IFilter imageFilter) + { + imageStream = imageFilter.GetDataForkStream(); + + imageStream.Seek(-4, SeekOrigin.End); + byte[] dscLenB = new byte[4]; + imageStream.Read(dscLenB, 0, 4); + int dscLen = BitConverter.ToInt32(dscLenB, 0); + + DicConsole.DebugWriteLine("DiscJuggler plugin", "dscLen = {0}", dscLen); + + if(dscLen >= imageStream.Length) return false; + + byte[] descriptor = new byte[dscLen]; + imageStream.Seek(-dscLen, SeekOrigin.End); + imageStream.Read(descriptor, 0, dscLen); + + // Sessions + if(descriptor[0] > 99 || descriptor[0] == 0) return false; + + // Seems all sessions start with this data + if(descriptor[1] != 0x00 || descriptor[3] != 0x00 || descriptor[4] != 0x00 || descriptor[5] != 0x00 || + descriptor[6] != 0x00 || descriptor[7] != 0x00 || descriptor[8] != 0x00 || descriptor[9] != 0x00 || + descriptor[10] != 0x01 || descriptor[11] != 0x00 || descriptor[12] != 0x00 || descriptor[13] != 0x00 || + descriptor[14] != 0xFF || descriptor[15] != 0xFF) return false; + + // Too many tracks + return descriptor[2] <= 99; + } + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscJuggler/Properties.cs b/DiscImageChef.DiscImages/DiscJuggler/Properties.cs new file mode 100644 index 000000000..aecfaf195 --- /dev/null +++ b/DiscImageChef.DiscImages/DiscJuggler/Properties.cs @@ -0,0 +1,54 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for DiscJuggler disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class DiscJuggler + { + public string Name => "DiscJuggler"; + public Guid Id => new Guid("2444DBC6-CD35-424C-A227-39B0C4DB01B2"); + public ImageInfo Info => imageInfo; + public string Format => "DiscJuggler"; + public List<Partition> Partitions { get; private set; } + public List<Track> Tracks { get; private set; } + public List<Session> Sessions { get; private set; } + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiscJuggler.cs b/DiscImageChef.DiscImages/DiscJuggler/Read.cs similarity index 93% rename from DiscImageChef.DiscImages/DiscJuggler.cs rename to DiscImageChef.DiscImages/DiscJuggler/Read.cs index 68d50189f..c0d23da68 100644 --- a/DiscImageChef.DiscImages/DiscJuggler.cs +++ b/DiscImageChef.DiscImages/DiscJuggler/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : DiscJuggler.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages DiscJuggler disc images. +// Reads DiscJuggler disc images. // // --[ License ] -------------------------------------------------------------- // @@ -42,88 +42,12 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // Support separate data files? Never seen a DiscJuggler image using them anyways... - // TODO: Too many unknowns to make this writable - public class DiscJuggler : IMediaImage + public partial class DiscJuggler { - byte[] cdtext; - ImageInfo imageInfo; - Stream imageStream; - Dictionary<uint, ulong> offsetmap; - Dictionary<uint, byte> trackFlags; - - public DiscJuggler() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = true, - HasSessions = true, - Version = null, - ApplicationVersion = null, - MediaTitle = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "DiscJuggler"; - public Guid Id => new Guid("2444DBC6-CD35-424C-A227-39B0C4DB01B2"); - public ImageInfo Info => imageInfo; - - public string Format => "DiscJuggler"; - - public List<Partition> Partitions { get; private set; } - - public List<Track> Tracks { get; private set; } - - public List<Session> Sessions { get; private set; } - - public bool Identify(IFilter imageFilter) - { - imageStream = imageFilter.GetDataForkStream(); - - imageStream.Seek(-4, SeekOrigin.End); - byte[] dscLenB = new byte[4]; - imageStream.Read(dscLenB, 0, 4); - int dscLen = BitConverter.ToInt32(dscLenB, 0); - - DicConsole.DebugWriteLine("DiscJuggler plugin", "dscLen = {0}", dscLen); - - if(dscLen >= imageStream.Length) return false; - - byte[] descriptor = new byte[dscLen]; - imageStream.Seek(-dscLen, SeekOrigin.End); - imageStream.Read(descriptor, 0, dscLen); - - // Sessions - if(descriptor[0] > 99 || descriptor[0] == 0) return false; - - // Seems all sessions start with this data - if(descriptor[1] != 0x00 || descriptor[3] != 0x00 || descriptor[4] != 0x00 || descriptor[5] != 0x00 || - descriptor[6] != 0x00 || descriptor[7] != 0x00 || descriptor[8] != 0x00 || descriptor[9] != 0x00 || - descriptor[10] != 0x01 || descriptor[11] != 0x00 || descriptor[12] != 0x00 || descriptor[13] != 0x00 || - descriptor[14] != 0xFF || descriptor[15] != 0xFF) return false; - - // Too many tracks - return descriptor[2] <= 99; - } - - public bool Open(IFilter imageFilter) + public bool Open(IFilter imageFilter) { imageStream = imageFilter.GetDataForkStream(); @@ -1184,17 +1108,5 @@ namespace DiscImageChef.DiscImages return null; } - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - static MediaType DecodeCdiMediumType(ushort type) - { - switch(type) - { - case 56: return MediaType.DVDROM; - case 152: return MediaType.CDROM; - default: return MediaType.Unknown; - } - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42.cs b/DiscImageChef.DiscImages/DiskCopy42.cs deleted file mode 100644 index 4ca74c448..000000000 --- a/DiscImageChef.DiscImages/DiskCopy42.cs +++ /dev/null @@ -1,1199 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : DiskCopy42.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Apple DiskCopy 4.2 disc images, including unofficial modifications. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text.RegularExpressions; -using Claunia.Encoding; -using Claunia.RsrcFork; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; -using Encoding = System.Text.Encoding; -using Version = Resources.Version; - -namespace DiscImageChef.DiscImages -{ - // Checked using several images and strings inside Apple's DiskImages.framework - [SuppressMessage("ReSharper", "InconsistentNaming")] - public class DiskCopy42 : IWritableImage - { - // format byte - /// <summary>3.5", single side, double density, GCR</summary> - const byte kSonyFormat400K = 0x00; - /// <summary>3.5", double side, double density, GCR</summary> - const byte kSonyFormat800K = 0x01; - /// <summary>3.5", double side, double density, MFM</summary> - const byte kSonyFormat720K = 0x02; - /// <summary>3.5", double side, high density, MFM</summary> - const byte kSonyFormat1440K = 0x03; - /// <summary>3.5", double side, high density, MFM, 21 sectors/track (aka, Microsoft DMF)</summary> - // Unchecked value</summary> - const byte kSonyFormat1680K = 0x04; - /// <summary>Defined by Sigma Seven's BLU</summary> - const byte kSigmaFormatTwiggy = 0x54; - /// <summary>Defined by LisaEm</summary> - const byte kNotStandardFormat = 0x5D; - // There should be a value for Apple HD20 hard disks, unknown... - // fmyByte byte - // Based on GCR nibble - // Always 0x02 for MFM disks - // Unknown for Apple HD20 - /// <summary>Defined by Sigma Seven's BLU</summary> - const byte kSigmaFmtByteTwiggy = 0x01; - /// <summary>3.5" single side double density GCR and MFM all use same code</summary> - const byte kSonyFmtByte400K = 0x02; - const byte kSonyFmtByte720K = kSonyFmtByte400K; - const byte kSonyFmtByte1440K = kSonyFmtByte400K; - const byte kSonyFmtByte1680K = kSonyFmtByte400K; - /// <summary>3.5" double side double density GCR, 512 bytes/sector, interleave 2:1</summary> - const byte kSonyFmtByte800K = 0x22; - /// <summary> - /// 3.5" double side double density GCR, 512 bytes/sector, interleave 2:1, incorrect value (but appears on - /// official documentation) - /// </summary> - const byte kSonyFmtByte800KIncorrect = 0x12; - /// <summary>3.5" double side double density GCR, ProDOS format, interleave 4:1</summary> - const byte kSonyFmtByteProDos = 0x24; - /// <summary>Unformatted sectors</summary> - const byte kInvalidFmtByte = 0x96; - /// <summary>Defined by LisaEm</summary> - const byte kFmtNotStandard = 0x93; - /// <summary>Used incorrectly by Mac OS X with certaing disk images</summary> - const byte kMacOSXFmtByte = 0x00; - const string REGEX_DCPY = @"(?<application>\S+)\s(?<version>\S+)\rData checksum=\$(?<checksum>\S+)$"; - /// <summary>Bytes per tag, should be 12</summary> - uint bptag; - - /// <summary>Start of data sectors in disk image, should be 0x58</summary> - uint dataOffset; - /// <summary>Disk image file</summary> - IFilter dc42ImageFilter; - /// <summary>Header of opened image</summary> - Dc42Header header; - ImageInfo imageInfo; - /// <summary>Start of tags in disk image, after data sectors</summary> - uint tagOffset; - bool twiggy; - byte[] twiggyCache; - byte[] twiggyCacheTags; - - FileStream writingStream; - - public DiskCopy42() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = "4.2", - Application = "Apple DiskCopy", - ApplicationVersion = "4.2", - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - public string Name => "Apple DiskCopy 4.2"; - public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - byte[] buffer = new byte[0x58]; - byte[] pString = new byte[64]; - stream.Read(buffer, 0, 0x58); - - // Incorrect pascal string length, not DC42 - if(buffer[0] > 63) return false; - - Dc42Header tmpHeader = new Dc42Header(); - - Array.Copy(buffer, 0, pString, 0, 64); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - tmpHeader.DiskName = StringHandlers.PascalToString(pString, Encoding.GetEncoding("macintosh")); - tmpHeader.DataSize = BigEndianBitConverter.ToUInt32(buffer, 0x40); - tmpHeader.TagSize = BigEndianBitConverter.ToUInt32(buffer, 0x44); - tmpHeader.DataChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x48); - tmpHeader.TagChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x4C); - tmpHeader.Format = buffer[0x50]; - tmpHeader.FmtByte = buffer[0x51]; - tmpHeader.Valid = buffer[0x52]; - tmpHeader.Reserved = buffer[0x53]; - - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.diskName = \"{0}\"", tmpHeader.DiskName); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.dataSize = {0} bytes", tmpHeader.DataSize); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.tagSize = {0} bytes", tmpHeader.TagSize); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.dataChecksum = 0x{0:X8}", tmpHeader.DataChecksum); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.tagChecksum = 0x{0:X8}", tmpHeader.TagChecksum); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.format = 0x{0:X2}", tmpHeader.Format); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.fmtByte = 0x{0:X2}", tmpHeader.FmtByte); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.valid = {0}", tmpHeader.Valid); - DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.reserved = {0}", tmpHeader.Reserved); - - if(tmpHeader.Valid != 1 || tmpHeader.Reserved != 0) return false; - - // Some versions seem to incorrectly create little endian fields - if(tmpHeader.DataSize + tmpHeader.TagSize + 0x54 != imageFilter.GetDataForkLength() && - tmpHeader.Format != kSigmaFormatTwiggy) - { - tmpHeader.DataSize = BitConverter.ToUInt32(buffer, 0x40); - tmpHeader.TagSize = BitConverter.ToUInt32(buffer, 0x44); - tmpHeader.DataChecksum = BitConverter.ToUInt32(buffer, 0x48); - tmpHeader.TagChecksum = BitConverter.ToUInt32(buffer, 0x4C); - - if(tmpHeader.DataSize + tmpHeader.TagSize + 0x54 != imageFilter.GetDataForkLength() && - tmpHeader.Format != kSigmaFormatTwiggy) return false; - } - - if(tmpHeader.Format != kSonyFormat400K && tmpHeader.Format != kSonyFormat800K && - tmpHeader.Format != kSonyFormat720K && tmpHeader.Format != kSonyFormat1440K && - tmpHeader.Format != kSonyFormat1680K && tmpHeader.Format != kSigmaFormatTwiggy && - tmpHeader.Format != kNotStandardFormat) - { - DicConsole.DebugWriteLine("DC42 plugin", "Unknown tmp_header.format = 0x{0:X2} value", - tmpHeader.Format); - - return false; - } - - if(tmpHeader.FmtByte != kSonyFmtByte400K && tmpHeader.FmtByte != kSonyFmtByte800K && - tmpHeader.FmtByte != kSonyFmtByte800KIncorrect && tmpHeader.FmtByte != kSonyFmtByteProDos && - tmpHeader.FmtByte != kInvalidFmtByte && tmpHeader.FmtByte != kSigmaFmtByteTwiggy && - tmpHeader.FmtByte != kFmtNotStandard && tmpHeader.FmtByte != kMacOSXFmtByte) - { - DicConsole.DebugWriteLine("DC42 plugin", "Unknown tmp_header.fmtByte = 0x{0:X2} value", - tmpHeader.FmtByte); - - return false; - } - - if(tmpHeader.FmtByte != kInvalidFmtByte) return true; - - DicConsole.DebugWriteLine("DC42 plugin", "Image says it's unformatted"); - - return false; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - byte[] buffer = new byte[0x58]; - byte[] pString = new byte[64]; - stream.Read(buffer, 0, 0x58); - IsWriting = false; - - // Incorrect pascal string length, not DC42 - if(buffer[0] > 63) return false; - - header = new Dc42Header(); - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - Array.Copy(buffer, 0, pString, 0, 64); - header.DiskName = StringHandlers.PascalToString(pString, Encoding.GetEncoding("macintosh")); - header.DataSize = BigEndianBitConverter.ToUInt32(buffer, 0x40); - header.TagSize = BigEndianBitConverter.ToUInt32(buffer, 0x44); - header.DataChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x48); - header.TagChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x4C); - header.Format = buffer[0x50]; - header.FmtByte = buffer[0x51]; - header.Valid = buffer[0x52]; - header.Reserved = buffer[0x53]; - - DicConsole.DebugWriteLine("DC42 plugin", "header.diskName = \"{0}\"", header.DiskName); - DicConsole.DebugWriteLine("DC42 plugin", "header.dataSize = {0} bytes", header.DataSize); - DicConsole.DebugWriteLine("DC42 plugin", "header.tagSize = {0} bytes", header.TagSize); - DicConsole.DebugWriteLine("DC42 plugin", "header.dataChecksum = 0x{0:X8}", header.DataChecksum); - DicConsole.DebugWriteLine("DC42 plugin", "header.tagChecksum = 0x{0:X8}", header.TagChecksum); - DicConsole.DebugWriteLine("DC42 plugin", "header.format = 0x{0:X2}", header.Format); - DicConsole.DebugWriteLine("DC42 plugin", "header.fmtByte = 0x{0:X2}", header.FmtByte); - DicConsole.DebugWriteLine("DC42 plugin", "header.valid = {0}", header.Valid); - DicConsole.DebugWriteLine("DC42 plugin", "header.reserved = {0}", header.Reserved); - - if(header.Valid != 1 || header.Reserved != 0) return false; - - // Some versions seem to incorrectly create little endian fields - if(header.DataSize + header.TagSize + 0x54 != imageFilter.GetDataForkLength() && - header.Format != kSigmaFormatTwiggy) - { - header.DataSize = BitConverter.ToUInt32(buffer, 0x40); - header.TagSize = BitConverter.ToUInt32(buffer, 0x44); - header.DataChecksum = BitConverter.ToUInt32(buffer, 0x48); - header.TagChecksum = BitConverter.ToUInt32(buffer, 0x4C); - - if(header.DataSize + header.TagSize + 0x54 != imageFilter.GetDataForkLength() && - header.Format != kSigmaFormatTwiggy) return false; - } - - if(header.Format != kSonyFormat400K && header.Format != kSonyFormat800K && - header.Format != kSonyFormat720K && header.Format != kSonyFormat1440K && - header.Format != kSonyFormat1680K && header.Format != kSigmaFormatTwiggy && - header.Format != kNotStandardFormat) - { - DicConsole.DebugWriteLine("DC42 plugin", "Unknown header.format = 0x{0:X2} value", header.Format); - - return false; - } - - if(header.FmtByte != kSonyFmtByte400K && header.FmtByte != kSonyFmtByte800K && - header.FmtByte != kSonyFmtByte800KIncorrect && header.FmtByte != kSonyFmtByteProDos && - header.FmtByte != kInvalidFmtByte && header.FmtByte != kSigmaFmtByteTwiggy && - header.FmtByte != kFmtNotStandard && header.FmtByte != kMacOSXFmtByte) - { - DicConsole.DebugWriteLine("DC42 plugin", "Unknown tmp_header.fmtByte = 0x{0:X2} value", header.FmtByte); - - return false; - } - - if(header.FmtByte == kInvalidFmtByte) - { - DicConsole.DebugWriteLine("DC42 plugin", "Image says it's unformatted"); - - return false; - } - - dataOffset = 0x54; - tagOffset = header.TagSize != 0 ? 0x54 + header.DataSize : 0; - imageInfo.SectorSize = 512; - bptag = (uint)(header.TagSize != 0 ? 12 : 0); - dc42ImageFilter = imageFilter; - - imageInfo.Sectors = header.DataSize / 512; - - if(header.TagSize != 0) - { - bptag = (uint)(header.TagSize / imageInfo.Sectors); - DicConsole.DebugWriteLine("DC42 plugin", "bptag = {0} bytes", bptag); - - if(bptag != 12 && bptag != 20 && bptag != 24) - { - DicConsole.DebugWriteLine("DC42 plugin", "Unknown tag size"); - return false; - } - - imageInfo.ReadableSectorTags.Add(SectorTagType.AppleSectorTag); - } - - imageInfo.ImageSize = imageInfo.Sectors * imageInfo.SectorSize + imageInfo.Sectors * bptag; - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = header.DiskName; - - switch(header.Format) - { - case kSonyFormat400K: - imageInfo.MediaType = imageInfo.Sectors == 1600 ? MediaType.AppleSonyDS : MediaType.AppleSonySS; - break; - case kSonyFormat800K: - imageInfo.MediaType = MediaType.AppleSonyDS; - break; - case kSonyFormat720K: - imageInfo.MediaType = MediaType.DOS_35_DS_DD_9; - break; - case kSonyFormat1440K: - imageInfo.MediaType = MediaType.DOS_35_HD; - break; - case kSonyFormat1680K: - imageInfo.MediaType = MediaType.DMF; - break; - case kSigmaFormatTwiggy: - imageInfo.MediaType = MediaType.AppleFileWare; - break; - case kNotStandardFormat: - switch(imageInfo.Sectors) - { - case 9728: - imageInfo.MediaType = MediaType.AppleProfile; - break; - case 19456: - imageInfo.MediaType = MediaType.AppleProfile; - break; - case 38912: - imageInfo.MediaType = MediaType.AppleWidget; - break; - case 39040: - imageInfo.MediaType = MediaType.AppleHD20; - break; - default: - imageInfo.MediaType = MediaType.Unknown; - break; - } - - break; - default: - imageInfo.MediaType = MediaType.Unknown; - break; - } - - if(imageInfo.MediaType == MediaType.AppleFileWare) - { - byte[] data = new byte[header.DataSize]; - byte[] tags = new byte[header.TagSize]; - - twiggyCache = new byte[header.DataSize]; - twiggyCacheTags = new byte[header.TagSize]; - twiggy = true; - - Stream datastream = imageFilter.GetDataForkStream(); - datastream.Seek(dataOffset, SeekOrigin.Begin); - datastream.Read(data, 0, (int)header.DataSize); - - Stream tagstream = imageFilter.GetDataForkStream(); - tagstream.Seek(tagOffset, SeekOrigin.Begin); - tagstream.Read(tags, 0, (int)header.TagSize); - - ushort mfsMagic = BigEndianBitConverter.ToUInt16(data, data.Length / 2 + 0x400); - ushort mfsAllBlocks = BigEndianBitConverter.ToUInt16(data, data.Length / 2 + 0x412); - - // Detect a Macintosh Twiggy - if(mfsMagic == 0xD2D7 && mfsAllBlocks == 422) - { - DicConsole.DebugWriteLine("DC42 plugin", "Macintosh Twiggy detected, reversing disk sides"); - Array.Copy(data, header.DataSize / 2, twiggyCache, 0, header.DataSize / 2); - Array.Copy(tags, header.TagSize / 2, twiggyCacheTags, 0, header.TagSize / 2); - Array.Copy(data, 0, twiggyCache, header.DataSize / 2, header.DataSize / 2); - Array.Copy(tags, 0, twiggyCacheTags, header.TagSize / 2, header.TagSize / 2); - } - else - { - DicConsole.DebugWriteLine("DC42 plugin", "Lisa Twiggy detected, reversing second half of disk"); - Array.Copy(data, 0, twiggyCache, 0, header.DataSize / 2); - Array.Copy(tags, 0, twiggyCacheTags, 0, header.TagSize / 2); - - int copiedSectors = 0; - int sectorsToCopy = 0; - - for(int i = 0; i < 46; i++) - { - if(i >= 0 && i <= 3) sectorsToCopy = 22; - if(i >= 4 && i <= 10) sectorsToCopy = 21; - if(i >= 11 && i <= 16) sectorsToCopy = 20; - if(i >= 17 && i <= 22) sectorsToCopy = 19; - if(i >= 23 && i <= 28) sectorsToCopy = 18; - if(i >= 29 && i <= 34) sectorsToCopy = 17; - if(i >= 35 && i <= 41) sectorsToCopy = 16; - if(i >= 42 && i <= 45) sectorsToCopy = 15; - - Array.Copy(data, header.DataSize / 2 + copiedSectors * 512, twiggyCache, - twiggyCache.Length - copiedSectors * 512 - sectorsToCopy * 512, sectorsToCopy * 512); - Array.Copy(tags, header.TagSize / 2 + copiedSectors * bptag, - twiggyCacheTags, - twiggyCacheTags.Length - copiedSectors * bptag - sectorsToCopy * bptag, - sectorsToCopy * bptag); - - copiedSectors += sectorsToCopy; - } - } - } - - try - { - if(imageFilter.HasResourceFork()) - { - ResourceFork rsrcFork = new ResourceFork(imageFilter.GetResourceForkStream()); - if(rsrcFork.ContainsKey(0x76657273)) - { - Resource versRsrc = rsrcFork.GetResource(0x76657273); - - byte[] vers = versRsrc?.GetResource(versRsrc.GetIds()[0]); - - if(vers != null) - { - Version version = new Version(vers); - - string release = null; - string dev = null; - string pre = null; - - string major = $"{version.MajorVersion}"; - string minor = $".{version.MinorVersion / 10}"; - if(version.MinorVersion % 10 > 0) release = $".{version.MinorVersion % 10}"; - switch(version.DevStage) - { - case Version.DevelopmentStage.Alpha: - dev = "a"; - break; - case Version.DevelopmentStage.Beta: - dev = "b"; - break; - case Version.DevelopmentStage.PreAlpha: - dev = "d"; - break; - } - - if(dev == null && version.PreReleaseVersion > 0) dev = "f"; - - if(dev != null) pre = $"{version.PreReleaseVersion}"; - - imageInfo.ApplicationVersion = $"{major}{minor}{release}{dev}{pre}"; - imageInfo.Application = version.VersionString; - imageInfo.Comments = version.VersionMessage; - } - } - - if(rsrcFork.ContainsKey(0x64437079)) - { - Resource dCpyRsrc = rsrcFork.GetResource(0x64437079); - if(dCpyRsrc != null) - { - string dCpy = StringHandlers.PascalToString(dCpyRsrc.GetResource(dCpyRsrc.GetIds()[0]), - Encoding.GetEncoding("macintosh")); - Regex dCpyEx = new Regex(REGEX_DCPY); - Match dCpyMatch = dCpyEx.Match(dCpy); - - if(dCpyMatch.Success) - { - imageInfo.Application = dCpyMatch.Groups["application"].Value; - imageInfo.ApplicationVersion = dCpyMatch.Groups["version"].Value; - } - } - } - } - } - catch(InvalidCastException) { } - - DicConsole.DebugWriteLine("DC42 plugin", "Image application = {0} version {1}", imageInfo.Application, - imageInfo.ApplicationVersion); - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - DicConsole.VerboseWriteLine("DiskCopy 4.2 image contains a disk of type {0}", imageInfo.MediaType); - - switch(imageInfo.MediaType) - { - case MediaType.AppleSonySS: - imageInfo.Cylinders = 80; - imageInfo.Heads = 1; - imageInfo.SectorsPerTrack = 10; - break; - case MediaType.AppleSonyDS: - imageInfo.Cylinders = 80; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 10; - break; - case MediaType.DOS_35_DS_DD_9: - imageInfo.Cylinders = 80; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 9; - break; - case MediaType.DOS_35_HD: - imageInfo.Cylinders = 80; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 18; - break; - case MediaType.DMF: - imageInfo.Cylinders = 80; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 21; - break; - case MediaType.AppleProfile: - switch(imageInfo.Sectors) - { - case 9728: - imageInfo.Cylinders = 152; - break; - case 19456: - imageInfo.Cylinders = 304; - break; - } - - imageInfo.Heads = 4; - imageInfo.SectorsPerTrack = 16; - break; - case MediaType.AppleWidget: - imageInfo.Cylinders = 608; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 16; - break; - case MediaType.AppleHD20: - imageInfo.Cylinders = 610; - imageInfo.Heads = 2; - imageInfo.SectorsPerTrack = 16; - break; - default: - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - break; - } - - return true; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - byte[] data = new byte[header.DataSize]; - byte[] tags = new byte[header.TagSize]; - uint tagsChk = 0; - - DicConsole.DebugWriteLine("DC42 plugin", "Reading data"); - Stream datastream = dc42ImageFilter.GetDataForkStream(); - datastream.Seek(dataOffset, SeekOrigin.Begin); - datastream.Read(data, 0, (int)header.DataSize); - - DicConsole.DebugWriteLine("DC42 plugin", "Calculating data checksum"); - uint dataChk = DC42CheckSum(data); - DicConsole.DebugWriteLine("DC42 plugin", "Calculated data checksum = 0x{0:X8}", dataChk); - DicConsole.DebugWriteLine("DC42 plugin", "Stored data checksum = 0x{0:X8}", header.DataChecksum); - - if(header.TagSize <= 0) return dataChk == header.DataChecksum && tagsChk == header.TagChecksum; - - DicConsole.DebugWriteLine("DC42 plugin", "Reading tags"); - Stream tagstream = dc42ImageFilter.GetDataForkStream(); - tagstream.Seek(tagOffset, SeekOrigin.Begin); - tagstream.Read(tags, 0, (int)header.TagSize); - - DicConsole.DebugWriteLine("DC42 plugin", "Calculating tag checksum"); - tagsChk = DC42CheckSum(tags); - DicConsole.DebugWriteLine("DC42 plugin", "Calculated tag checksum = 0x{0:X8}", tagsChk); - DicConsole.DebugWriteLine("DC42 plugin", "Stored tag checksum = 0x{0:X8}", header.TagChecksum); - - return dataChk == header.DataChecksum && tagsChk == header.TagChecksum; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - return ReadSectorsTag(sectorAddress, 1, tag); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - if(twiggy) - Array.Copy(twiggyCache, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, - length * imageInfo.SectorSize); - else - { - Stream stream = dc42ImageFilter.GetDataForkStream(); - stream.Seek((long)(dataOffset + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - } - - return buffer; - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - if(tag != SectorTagType.AppleSectorTag) - throw new FeatureUnsupportedImageException($"Tag {tag} not supported by image format"); - - if(header.TagSize == 0) throw new FeatureNotPresentImageException("Disk image does not have tags"); - - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * bptag]; - - if(twiggy) Array.Copy(twiggyCacheTags, (int)sectorAddress * bptag, buffer, 0, length * bptag); - else - { - Stream stream = dc42ImageFilter.GetDataForkStream(); - stream.Seek((long)(tagOffset + sectorAddress * bptag), SeekOrigin.Begin); - stream.Read(buffer, 0, (int)(length * bptag)); - } - - return buffer; - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - return ReadSectorsLong(sectorAddress, 1); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] data = ReadSectors(sectorAddress, length); - byte[] tags = ReadSectorsTag(sectorAddress, length, SectorTagType.AppleSectorTag); - byte[] buffer = new byte[data.Length + tags.Length]; - - for(uint i = 0; i < length; i++) - { - Array.Copy(data, i * imageInfo.SectorSize, buffer, i * (imageInfo.SectorSize + bptag), - imageInfo.SectorSize); - Array.Copy(tags, i * bptag, buffer, i * (imageInfo.SectorSize + bptag) + imageInfo.SectorSize, bptag); - } - - return buffer; - } - - public string Format => "Apple DiskCopy 4.2"; - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new[] {SectorTagType.AppleSectorTag}; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.AppleFileWare, MediaType.AppleHD20, MediaType.AppleProfile, MediaType.AppleSonyDS, - MediaType.AppleSonySS, MediaType.AppleWidget, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, - MediaType.DMF - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new[] {("macosx", typeof(bool), "Use Mac OS X format byte")}; - public IEnumerable<string> KnownExtensions => new[] {".dc42", ".diskcopy42", ".image"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - header = new Dc42Header(); - bool tags = false; - bool macosx = false; - - if(options != null && options.TryGetValue("macosx", out string tmpOption)) - bool.TryParse(tmpOption, out macosx); - - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - switch(mediaType) - { - case MediaType.AppleFileWare: - header.FmtByte = kSigmaFmtByteTwiggy; - header.Format = kSigmaFormatTwiggy; - twiggy = true; - // TODO - ErrorMessage = "Twiggy write support not yet implemented"; - return false; - case MediaType.AppleHD20: - if(sectors != 39040) - { - ErrorMessage = "Incorrect number of sectors for Apple HD20 image"; - return false; - } - - header.FmtByte = kFmtNotStandard; - header.Format = kNotStandardFormat; - tags = true; - break; - case MediaType.AppleProfile: - if(sectors != 9728 && sectors != 19456) - { - ErrorMessage = "Incorrect number of sectors for Apple Profile image"; - return false; - } - - header.FmtByte = kFmtNotStandard; - header.Format = kNotStandardFormat; - tags = true; - break; - case MediaType.AppleSonyDS: - if(sectors != 1600) - { - ErrorMessage = "Incorrect number of sectors for Apple MF2DD image"; - return false; - } - - header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte800K; - header.Format = kSonyFormat800K; - tags = true; - break; - case MediaType.AppleSonySS: - if(sectors != 800) - { - ErrorMessage = "Incorrect number of sectors for Apple MF1DD image"; - return false; - } - - header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte400K; - header.Format = kSonyFormat400K; - tags = true; - break; - case MediaType.AppleWidget: - if(sectors != 39040) - { - ErrorMessage = "Incorrect number of sectors for Apple Widget image"; - return false; - } - - header.FmtByte = kFmtNotStandard; - header.Format = kNotStandardFormat; - tags = true; - break; - case MediaType.DOS_35_DS_DD_9: - if(sectors != 1440) - { - ErrorMessage = "Incorrect number of sectors for MF2DD image"; - return false; - } - - header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte720K; - header.Format = kSonyFormat720K; - break; - case MediaType.DOS_35_HD: - if(sectors != 2880) - { - ErrorMessage = "Incorrect number of sectors for MF2HD image"; - return false; - } - - header.Format = kSonyFmtByte1440K; - header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte1440K; - break; - case MediaType.DMF: - if(sectors != 3360) - { - ErrorMessage = "Incorrect number of sectors for DMF image"; - return false; - } - - header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte1680K; - header.Format = kSonyFormat1680K; - break; - default: - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - dataOffset = 0x54; - tagOffset = header.TagSize != 0 ? 0x54 + header.DataSize : 0; - header.DiskName = "-DiscImageChef converted image-"; - header.Valid = 1; - header.DataSize = (uint)(sectors * 512); - if(tags) header.TagSize = (uint)(sectors * 12); - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(header.TagSize == 0) - { - ErrorMessage = "Image does not support tags"; - return false; - } - - if(data.Length != 524) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, 512); - writingStream.Seek((long)(tagOffset + sectorAddress * 12), SeekOrigin.Begin); - writingStream.Write(data, 512, 12); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(header.TagSize == 0) - { - ErrorMessage = "Image does not support tags"; - return false; - } - - if(data.Length % 524 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - for(uint i = 0; i < length; i++) - { - writingStream.Seek((long)(dataOffset + (sectorAddress + i) * 512), SeekOrigin.Begin); - writingStream.Write(data, (int)(i * 524 + 0), 512); - writingStream.Seek((long)(tagOffset + (sectorAddress + i) * 12), - SeekOrigin.Begin); - writingStream.Write(data, (int)(i * 524 + 512), - 12); - } - - ErrorMessage = ""; - return true; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - // No tags where written - if(writingStream.Length == 0x54 + header.DataSize) header.TagSize = 0; - - writingStream.Seek(0x54, SeekOrigin.Begin); - byte[] data = new byte[header.DataSize]; - writingStream.Read(data, 0, (int)header.DataSize); - header.DataChecksum = DC42CheckSum(data); - writingStream.Seek(0x54 + header.DataSize, SeekOrigin.Begin); - data = new byte[header.TagSize]; - writingStream.Read(data, 0, (int)header.TagSize); - header.TagChecksum = DC42CheckSum(data); - - writingStream.Seek(0, SeekOrigin.Begin); - if(header.DiskName.Length > 63) header.DiskName = header.DiskName.Substring(0, 63); - writingStream.WriteByte((byte)header.DiskName.Length); - Encoding macRoman = new MacRoman(); - writingStream.Write(macRoman.GetBytes(header.DiskName), 0, header.DiskName.Length); - - writingStream.Seek(64, SeekOrigin.Begin); - writingStream.Write(BigEndianBitConverter.GetBytes(header.DataSize), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(header.TagSize), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(header.DataChecksum), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(header.TagChecksum), 0, 4); - writingStream.WriteByte(header.Format); - writingStream.WriteByte(header.FmtByte); - writingStream.WriteByte(1); - writingStream.WriteByte(0); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - header.DiskName = metadata.MediaTitle ?? "-DiscImageChef converted image-"; - - return true; - } - - static uint DC42CheckSum(byte[] buffer) - { - uint dc42Chk = 0; - if((buffer.Length & 0x01) == 0x01) return 0xFFFFFFFF; - - for(uint i = 0; i < buffer.Length; i += 2) - { - dc42Chk += (uint)(buffer[i] << 8); - dc42Chk += buffer[i + 1]; - dc42Chk = (dc42Chk >> 1) | (dc42Chk << 31); - } - - return dc42Chk; - } - - ~DiskCopy42() - { - Close(); - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - // DiskCopy 4.2 header, big-endian, data-fork, start of file, 84 bytes - struct Dc42Header - { - /// <summary>0x00, 64 bytes, pascal string, disk name or "-not a Macintosh disk-", filled with garbage</summary> - public string DiskName; - /// <summary>0x40, size of data in bytes (usually sectors*512)</summary> - public uint DataSize; - /// <summary>0x44, size of tags in bytes (usually sectors*12)</summary> - public uint TagSize; - /// <summary>0x48, checksum of data bytes</summary> - public uint DataChecksum; - /// <summary>0x4C, checksum of tag bytes</summary> - public uint TagChecksum; - /// <summary>0x50, format of disk, see constants</summary> - public byte Format; - /// <summary>0x51, format of sectors, see constants</summary> - public byte FmtByte; - /// <summary>0x52, is disk image valid? always 0x01</summary> - public byte Valid; - /// <summary>0x53, reserved, always 0x00</summary> - public byte Reserved; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Constants.cs b/DiscImageChef.DiscImages/DiskCopy42/Constants.cs new file mode 100644 index 000000000..ab58ff27c --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Constants.cs @@ -0,0 +1,82 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + // format byte + /// <summary>3.5", single side, double density, GCR</summary> + const byte kSonyFormat400K = 0x00; + /// <summary>3.5", double side, double density, GCR</summary> + const byte kSonyFormat800K = 0x01; + /// <summary>3.5", double side, double density, MFM</summary> + const byte kSonyFormat720K = 0x02; + /// <summary>3.5", double side, high density, MFM</summary> + const byte kSonyFormat1440K = 0x03; + /// <summary>3.5", double side, high density, MFM, 21 sectors/track (aka, Microsoft DMF)</summary> + // Unchecked value</summary> + const byte kSonyFormat1680K = 0x04; + /// <summary>Defined by Sigma Seven's BLU</summary> + const byte kSigmaFormatTwiggy = 0x54; + /// <summary>Defined by LisaEm</summary> + const byte kNotStandardFormat = 0x5D; + // There should be a value for Apple HD20 hard disks, unknown... + // fmyByte byte + // Based on GCR nibble + // Always 0x02 for MFM disks + // Unknown for Apple HD20 + /// <summary>Defined by Sigma Seven's BLU</summary> + const byte kSigmaFmtByteTwiggy = 0x01; + /// <summary>3.5" single side double density GCR and MFM all use same code</summary> + const byte kSonyFmtByte400K = 0x02; + const byte kSonyFmtByte720K = kSonyFmtByte400K; + const byte kSonyFmtByte1440K = kSonyFmtByte400K; + const byte kSonyFmtByte1680K = kSonyFmtByte400K; + /// <summary>3.5" double side double density GCR, 512 bytes/sector, interleave 2:1</summary> + const byte kSonyFmtByte800K = 0x22; + /// <summary> + /// 3.5" double side double density GCR, 512 bytes/sector, interleave 2:1, incorrect value (but appears on + /// official documentation) + /// </summary> + const byte kSonyFmtByte800KIncorrect = 0x12; + /// <summary>3.5" double side double density GCR, ProDOS format, interleave 4:1</summary> + const byte kSonyFmtByteProDos = 0x24; + /// <summary>Unformatted sectors</summary> + const byte kInvalidFmtByte = 0x96; + /// <summary>Defined by LisaEm</summary> + const byte kFmtNotStandard = 0x93; + /// <summary>Used incorrectly by Mac OS X with certaing disk images</summary> + const byte kMacOSXFmtByte = 0x00; + const string REGEX_DCPY = @"(?<application>\S+)\s(?<version>\S+)\rData checksum=\$(?<checksum>\S+)$"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/DiskCopy42.cs b/DiscImageChef.DiscImages/DiskCopy42/DiskCopy42.cs new file mode 100644 index 000000000..54712337f --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/DiskCopy42.cs @@ -0,0 +1,94 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : DiskCopy42.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Apple DiskCopy 4.2 disk images, including unofficial modifications. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // Checked using several images and strings inside Apple's DiskImages.framework + [SuppressMessage("ReSharper", "InconsistentNaming")] + public partial class DiskCopy42 : IWritableImage + { + /// <summary>Bytes per tag, should be 12</summary> + uint bptag; + /// <summary>Start of data sectors in disk image, should be 0x58</summary> + uint dataOffset; + /// <summary>Disk image file</summary> + IFilter dc42ImageFilter; + /// <summary>Header of opened image</summary> + Dc42Header header; + ImageInfo imageInfo; + /// <summary>Start of tags in disk image, after data sectors</summary> + uint tagOffset; + bool twiggy; + byte[] twiggyCache; + byte[] twiggyCacheTags; + FileStream writingStream; + + public DiskCopy42() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = "4.2", + Application = "Apple DiskCopy", + ApplicationVersion = "4.2", + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + + ~DiskCopy42() + { + Close(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Helpers.cs b/DiscImageChef.DiscImages/DiskCopy42/Helpers.cs new file mode 100644 index 000000000..2d2add5ff --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Helpers.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + static uint DC42CheckSum(byte[] buffer) + { + uint dc42Chk = 0; + if((buffer.Length & 0x01) == 0x01) return 0xFFFFFFFF; + + for(uint i = 0; i < buffer.Length; i += 2) + { + dc42Chk += (uint)(buffer[i] << 8); + dc42Chk += buffer[i + 1]; + dc42Chk = (dc42Chk >> 1) | (dc42Chk << 31); + } + + return dc42Chk; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Identify.cs b/DiscImageChef.DiscImages/DiskCopy42/Identify.cs new file mode 100644 index 000000000..523a339bb --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Identify.cs @@ -0,0 +1,124 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using Claunia.Encoding; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[0x58]; + byte[] pString = new byte[64]; + stream.Read(buffer, 0, 0x58); + + // Incorrect pascal string length, not DC42 + if(buffer[0] > 63) return false; + + Dc42Header tmpHeader = new Dc42Header(); + + Array.Copy(buffer, 0, pString, 0, 64); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + tmpHeader.DiskName = StringHandlers.PascalToString(pString, Encoding.GetEncoding("macintosh")); + tmpHeader.DataSize = BigEndianBitConverter.ToUInt32(buffer, 0x40); + tmpHeader.TagSize = BigEndianBitConverter.ToUInt32(buffer, 0x44); + tmpHeader.DataChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x48); + tmpHeader.TagChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x4C); + tmpHeader.Format = buffer[0x50]; + tmpHeader.FmtByte = buffer[0x51]; + tmpHeader.Valid = buffer[0x52]; + tmpHeader.Reserved = buffer[0x53]; + + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.diskName = \"{0}\"", tmpHeader.DiskName); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.dataSize = {0} bytes", tmpHeader.DataSize); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.tagSize = {0} bytes", tmpHeader.TagSize); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.dataChecksum = 0x{0:X8}", tmpHeader.DataChecksum); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.tagChecksum = 0x{0:X8}", tmpHeader.TagChecksum); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.format = 0x{0:X2}", tmpHeader.Format); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.fmtByte = 0x{0:X2}", tmpHeader.FmtByte); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.valid = {0}", tmpHeader.Valid); + DicConsole.DebugWriteLine("DC42 plugin", "tmp_header.reserved = {0}", tmpHeader.Reserved); + + if(tmpHeader.Valid != 1 || tmpHeader.Reserved != 0) return false; + + // Some versions seem to incorrectly create little endian fields + if(tmpHeader.DataSize + tmpHeader.TagSize + 0x54 != imageFilter.GetDataForkLength() && + tmpHeader.Format != kSigmaFormatTwiggy) + { + tmpHeader.DataSize = BitConverter.ToUInt32(buffer, 0x40); + tmpHeader.TagSize = BitConverter.ToUInt32(buffer, 0x44); + tmpHeader.DataChecksum = BitConverter.ToUInt32(buffer, 0x48); + tmpHeader.TagChecksum = BitConverter.ToUInt32(buffer, 0x4C); + + if(tmpHeader.DataSize + tmpHeader.TagSize + 0x54 != imageFilter.GetDataForkLength() && + tmpHeader.Format != kSigmaFormatTwiggy) return false; + } + + if(tmpHeader.Format != kSonyFormat400K && tmpHeader.Format != kSonyFormat800K && + tmpHeader.Format != kSonyFormat720K && tmpHeader.Format != kSonyFormat1440K && + tmpHeader.Format != kSonyFormat1680K && tmpHeader.Format != kSigmaFormatTwiggy && + tmpHeader.Format != kNotStandardFormat) + { + DicConsole.DebugWriteLine("DC42 plugin", "Unknown tmp_header.format = 0x{0:X2} value", + tmpHeader.Format); + + return false; + } + + if(tmpHeader.FmtByte != kSonyFmtByte400K && tmpHeader.FmtByte != kSonyFmtByte800K && + tmpHeader.FmtByte != kSonyFmtByte800KIncorrect && tmpHeader.FmtByte != kSonyFmtByteProDos && + tmpHeader.FmtByte != kInvalidFmtByte && tmpHeader.FmtByte != kSigmaFmtByteTwiggy && + tmpHeader.FmtByte != kFmtNotStandard && tmpHeader.FmtByte != kMacOSXFmtByte) + { + DicConsole.DebugWriteLine("DC42 plugin", "Unknown tmp_header.fmtByte = 0x{0:X2} value", + tmpHeader.FmtByte); + + return false; + } + + if(tmpHeader.FmtByte != kInvalidFmtByte) return true; + + DicConsole.DebugWriteLine("DC42 plugin", "Image says it's unformatted"); + + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Properties.cs b/DiscImageChef.DiscImages/DiskCopy42/Properties.cs new file mode 100644 index 000000000..70a9359d0 --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Properties.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + public ImageInfo Info => imageInfo; + public string Name => "Apple DiskCopy 4.2"; + public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public string Format => "Apple DiskCopy 4.2"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new[] {SectorTagType.AppleSectorTag}; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.AppleFileWare, MediaType.AppleHD20, MediaType.AppleProfile, MediaType.AppleSonyDS, + MediaType.AppleSonySS, MediaType.AppleWidget, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, + MediaType.DMF + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new[] {("macosx", typeof(bool), "Use Mac OS X format byte")}; + public IEnumerable<string> KnownExtensions => new[] {".dc42", ".diskcopy42", ".image"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Read.cs b/DiscImageChef.DiscImages/DiskCopy42/Read.cs new file mode 100644 index 000000000..801c68143 --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Read.cs @@ -0,0 +1,511 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Text.RegularExpressions; +using Claunia.Encoding; +using Claunia.RsrcFork; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; +using Version = Resources.Version; + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[0x58]; + byte[] pString = new byte[64]; + stream.Read(buffer, 0, 0x58); + IsWriting = false; + + // Incorrect pascal string length, not DC42 + if(buffer[0] > 63) return false; + + header = new Dc42Header(); + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + Array.Copy(buffer, 0, pString, 0, 64); + header.DiskName = StringHandlers.PascalToString(pString, Encoding.GetEncoding("macintosh")); + header.DataSize = BigEndianBitConverter.ToUInt32(buffer, 0x40); + header.TagSize = BigEndianBitConverter.ToUInt32(buffer, 0x44); + header.DataChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x48); + header.TagChecksum = BigEndianBitConverter.ToUInt32(buffer, 0x4C); + header.Format = buffer[0x50]; + header.FmtByte = buffer[0x51]; + header.Valid = buffer[0x52]; + header.Reserved = buffer[0x53]; + + DicConsole.DebugWriteLine("DC42 plugin", "header.diskName = \"{0}\"", header.DiskName); + DicConsole.DebugWriteLine("DC42 plugin", "header.dataSize = {0} bytes", header.DataSize); + DicConsole.DebugWriteLine("DC42 plugin", "header.tagSize = {0} bytes", header.TagSize); + DicConsole.DebugWriteLine("DC42 plugin", "header.dataChecksum = 0x{0:X8}", header.DataChecksum); + DicConsole.DebugWriteLine("DC42 plugin", "header.tagChecksum = 0x{0:X8}", header.TagChecksum); + DicConsole.DebugWriteLine("DC42 plugin", "header.format = 0x{0:X2}", header.Format); + DicConsole.DebugWriteLine("DC42 plugin", "header.fmtByte = 0x{0:X2}", header.FmtByte); + DicConsole.DebugWriteLine("DC42 plugin", "header.valid = {0}", header.Valid); + DicConsole.DebugWriteLine("DC42 plugin", "header.reserved = {0}", header.Reserved); + + if(header.Valid != 1 || header.Reserved != 0) return false; + + // Some versions seem to incorrectly create little endian fields + if(header.DataSize + header.TagSize + 0x54 != imageFilter.GetDataForkLength() && + header.Format != kSigmaFormatTwiggy) + { + header.DataSize = BitConverter.ToUInt32(buffer, 0x40); + header.TagSize = BitConverter.ToUInt32(buffer, 0x44); + header.DataChecksum = BitConverter.ToUInt32(buffer, 0x48); + header.TagChecksum = BitConverter.ToUInt32(buffer, 0x4C); + + if(header.DataSize + header.TagSize + 0x54 != imageFilter.GetDataForkLength() && + header.Format != kSigmaFormatTwiggy) return false; + } + + if(header.Format != kSonyFormat400K && header.Format != kSonyFormat800K && + header.Format != kSonyFormat720K && header.Format != kSonyFormat1440K && + header.Format != kSonyFormat1680K && header.Format != kSigmaFormatTwiggy && + header.Format != kNotStandardFormat) + { + DicConsole.DebugWriteLine("DC42 plugin", "Unknown header.format = 0x{0:X2} value", header.Format); + + return false; + } + + if(header.FmtByte != kSonyFmtByte400K && header.FmtByte != kSonyFmtByte800K && + header.FmtByte != kSonyFmtByte800KIncorrect && header.FmtByte != kSonyFmtByteProDos && + header.FmtByte != kInvalidFmtByte && header.FmtByte != kSigmaFmtByteTwiggy && + header.FmtByte != kFmtNotStandard && header.FmtByte != kMacOSXFmtByte) + { + DicConsole.DebugWriteLine("DC42 plugin", "Unknown tmp_header.fmtByte = 0x{0:X2} value", header.FmtByte); + + return false; + } + + if(header.FmtByte == kInvalidFmtByte) + { + DicConsole.DebugWriteLine("DC42 plugin", "Image says it's unformatted"); + + return false; + } + + dataOffset = 0x54; + tagOffset = header.TagSize != 0 ? 0x54 + header.DataSize : 0; + imageInfo.SectorSize = 512; + bptag = (uint)(header.TagSize != 0 ? 12 : 0); + dc42ImageFilter = imageFilter; + + imageInfo.Sectors = header.DataSize / 512; + + if(header.TagSize != 0) + { + bptag = (uint)(header.TagSize / imageInfo.Sectors); + DicConsole.DebugWriteLine("DC42 plugin", "bptag = {0} bytes", bptag); + + if(bptag != 12 && bptag != 20 && bptag != 24) + { + DicConsole.DebugWriteLine("DC42 plugin", "Unknown tag size"); + return false; + } + + imageInfo.ReadableSectorTags.Add(SectorTagType.AppleSectorTag); + } + + imageInfo.ImageSize = imageInfo.Sectors * imageInfo.SectorSize + imageInfo.Sectors * bptag; + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = header.DiskName; + + switch(header.Format) + { + case kSonyFormat400K: + imageInfo.MediaType = imageInfo.Sectors == 1600 ? MediaType.AppleSonyDS : MediaType.AppleSonySS; + break; + case kSonyFormat800K: + imageInfo.MediaType = MediaType.AppleSonyDS; + break; + case kSonyFormat720K: + imageInfo.MediaType = MediaType.DOS_35_DS_DD_9; + break; + case kSonyFormat1440K: + imageInfo.MediaType = MediaType.DOS_35_HD; + break; + case kSonyFormat1680K: + imageInfo.MediaType = MediaType.DMF; + break; + case kSigmaFormatTwiggy: + imageInfo.MediaType = MediaType.AppleFileWare; + break; + case kNotStandardFormat: + switch(imageInfo.Sectors) + { + case 9728: + imageInfo.MediaType = MediaType.AppleProfile; + break; + case 19456: + imageInfo.MediaType = MediaType.AppleProfile; + break; + case 38912: + imageInfo.MediaType = MediaType.AppleWidget; + break; + case 39040: + imageInfo.MediaType = MediaType.AppleHD20; + break; + default: + imageInfo.MediaType = MediaType.Unknown; + break; + } + + break; + default: + imageInfo.MediaType = MediaType.Unknown; + break; + } + + if(imageInfo.MediaType == MediaType.AppleFileWare) + { + byte[] data = new byte[header.DataSize]; + byte[] tags = new byte[header.TagSize]; + + twiggyCache = new byte[header.DataSize]; + twiggyCacheTags = new byte[header.TagSize]; + twiggy = true; + + Stream datastream = imageFilter.GetDataForkStream(); + datastream.Seek(dataOffset, SeekOrigin.Begin); + datastream.Read(data, 0, (int)header.DataSize); + + Stream tagstream = imageFilter.GetDataForkStream(); + tagstream.Seek(tagOffset, SeekOrigin.Begin); + tagstream.Read(tags, 0, (int)header.TagSize); + + ushort mfsMagic = BigEndianBitConverter.ToUInt16(data, data.Length / 2 + 0x400); + ushort mfsAllBlocks = BigEndianBitConverter.ToUInt16(data, data.Length / 2 + 0x412); + + // Detect a Macintosh Twiggy + if(mfsMagic == 0xD2D7 && mfsAllBlocks == 422) + { + DicConsole.DebugWriteLine("DC42 plugin", "Macintosh Twiggy detected, reversing disk sides"); + Array.Copy(data, header.DataSize / 2, twiggyCache, 0, header.DataSize / 2); + Array.Copy(tags, header.TagSize / 2, twiggyCacheTags, 0, header.TagSize / 2); + Array.Copy(data, 0, twiggyCache, header.DataSize / 2, header.DataSize / 2); + Array.Copy(tags, 0, twiggyCacheTags, header.TagSize / 2, header.TagSize / 2); + } + else + { + DicConsole.DebugWriteLine("DC42 plugin", "Lisa Twiggy detected, reversing second half of disk"); + Array.Copy(data, 0, twiggyCache, 0, header.DataSize / 2); + Array.Copy(tags, 0, twiggyCacheTags, 0, header.TagSize / 2); + + int copiedSectors = 0; + int sectorsToCopy = 0; + + for(int i = 0; i < 46; i++) + { + if(i >= 0 && i <= 3) sectorsToCopy = 22; + if(i >= 4 && i <= 10) sectorsToCopy = 21; + if(i >= 11 && i <= 16) sectorsToCopy = 20; + if(i >= 17 && i <= 22) sectorsToCopy = 19; + if(i >= 23 && i <= 28) sectorsToCopy = 18; + if(i >= 29 && i <= 34) sectorsToCopy = 17; + if(i >= 35 && i <= 41) sectorsToCopy = 16; + if(i >= 42 && i <= 45) sectorsToCopy = 15; + + Array.Copy(data, header.DataSize / 2 + copiedSectors * 512, twiggyCache, + twiggyCache.Length - copiedSectors * 512 - sectorsToCopy * 512, sectorsToCopy * 512); + Array.Copy(tags, header.TagSize / 2 + copiedSectors * bptag, + twiggyCacheTags, + twiggyCacheTags.Length - copiedSectors * bptag - sectorsToCopy * bptag, + sectorsToCopy * bptag); + + copiedSectors += sectorsToCopy; + } + } + } + + try + { + if(imageFilter.HasResourceFork()) + { + ResourceFork rsrcFork = new ResourceFork(imageFilter.GetResourceForkStream()); + if(rsrcFork.ContainsKey(0x76657273)) + { + Resource versRsrc = rsrcFork.GetResource(0x76657273); + + byte[] vers = versRsrc?.GetResource(versRsrc.GetIds()[0]); + + if(vers != null) + { + Version version = new Version(vers); + + string release = null; + string dev = null; + string pre = null; + + string major = $"{version.MajorVersion}"; + string minor = $".{version.MinorVersion / 10}"; + if(version.MinorVersion % 10 > 0) release = $".{version.MinorVersion % 10}"; + switch(version.DevStage) + { + case Version.DevelopmentStage.Alpha: + dev = "a"; + break; + case Version.DevelopmentStage.Beta: + dev = "b"; + break; + case Version.DevelopmentStage.PreAlpha: + dev = "d"; + break; + } + + if(dev == null && version.PreReleaseVersion > 0) dev = "f"; + + if(dev != null) pre = $"{version.PreReleaseVersion}"; + + imageInfo.ApplicationVersion = $"{major}{minor}{release}{dev}{pre}"; + imageInfo.Application = version.VersionString; + imageInfo.Comments = version.VersionMessage; + } + } + + if(rsrcFork.ContainsKey(0x64437079)) + { + Resource dCpyRsrc = rsrcFork.GetResource(0x64437079); + if(dCpyRsrc != null) + { + string dCpy = StringHandlers.PascalToString(dCpyRsrc.GetResource(dCpyRsrc.GetIds()[0]), + Encoding.GetEncoding("macintosh")); + Regex dCpyEx = new Regex(REGEX_DCPY); + Match dCpyMatch = dCpyEx.Match(dCpy); + + if(dCpyMatch.Success) + { + imageInfo.Application = dCpyMatch.Groups["application"].Value; + imageInfo.ApplicationVersion = dCpyMatch.Groups["version"].Value; + } + } + } + } + } + catch(InvalidCastException) { } + + DicConsole.DebugWriteLine("DC42 plugin", "Image application = {0} version {1}", imageInfo.Application, + imageInfo.ApplicationVersion); + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + DicConsole.VerboseWriteLine("DiskCopy 4.2 image contains a disk of type {0}", imageInfo.MediaType); + + switch(imageInfo.MediaType) + { + case MediaType.AppleSonySS: + imageInfo.Cylinders = 80; + imageInfo.Heads = 1; + imageInfo.SectorsPerTrack = 10; + break; + case MediaType.AppleSonyDS: + imageInfo.Cylinders = 80; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 10; + break; + case MediaType.DOS_35_DS_DD_9: + imageInfo.Cylinders = 80; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 9; + break; + case MediaType.DOS_35_HD: + imageInfo.Cylinders = 80; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 18; + break; + case MediaType.DMF: + imageInfo.Cylinders = 80; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 21; + break; + case MediaType.AppleProfile: + switch(imageInfo.Sectors) + { + case 9728: + imageInfo.Cylinders = 152; + break; + case 19456: + imageInfo.Cylinders = 304; + break; + } + + imageInfo.Heads = 4; + imageInfo.SectorsPerTrack = 16; + break; + case MediaType.AppleWidget: + imageInfo.Cylinders = 608; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 16; + break; + case MediaType.AppleHD20: + imageInfo.Cylinders = 610; + imageInfo.Heads = 2; + imageInfo.SectorsPerTrack = 16; + break; + default: + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + break; + } + + return true; + } + + public bool? VerifyMediaImage() + { + byte[] data = new byte[header.DataSize]; + byte[] tags = new byte[header.TagSize]; + uint tagsChk = 0; + + DicConsole.DebugWriteLine("DC42 plugin", "Reading data"); + Stream datastream = dc42ImageFilter.GetDataForkStream(); + datastream.Seek(dataOffset, SeekOrigin.Begin); + datastream.Read(data, 0, (int)header.DataSize); + + DicConsole.DebugWriteLine("DC42 plugin", "Calculating data checksum"); + uint dataChk = DC42CheckSum(data); + DicConsole.DebugWriteLine("DC42 plugin", "Calculated data checksum = 0x{0:X8}", dataChk); + DicConsole.DebugWriteLine("DC42 plugin", "Stored data checksum = 0x{0:X8}", header.DataChecksum); + + if(header.TagSize <= 0) return dataChk == header.DataChecksum && tagsChk == header.TagChecksum; + + DicConsole.DebugWriteLine("DC42 plugin", "Reading tags"); + Stream tagstream = dc42ImageFilter.GetDataForkStream(); + tagstream.Seek(tagOffset, SeekOrigin.Begin); + tagstream.Read(tags, 0, (int)header.TagSize); + + DicConsole.DebugWriteLine("DC42 plugin", "Calculating tag checksum"); + tagsChk = DC42CheckSum(tags); + DicConsole.DebugWriteLine("DC42 plugin", "Calculated tag checksum = 0x{0:X8}", tagsChk); + DicConsole.DebugWriteLine("DC42 plugin", "Stored tag checksum = 0x{0:X8}", header.TagChecksum); + + return dataChk == header.DataChecksum && tagsChk == header.TagChecksum; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + return ReadSectorsTag(sectorAddress, 1, tag); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + if(twiggy) + Array.Copy(twiggyCache, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, + length * imageInfo.SectorSize); + else + { + Stream stream = dc42ImageFilter.GetDataForkStream(); + stream.Seek((long)(dataOffset + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + } + + return buffer; + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + if(tag != SectorTagType.AppleSectorTag) + throw new FeatureUnsupportedImageException($"Tag {tag} not supported by image format"); + + if(header.TagSize == 0) throw new FeatureNotPresentImageException("Disk image does not have tags"); + + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * bptag]; + + if(twiggy) Array.Copy(twiggyCacheTags, (int)sectorAddress * bptag, buffer, 0, length * bptag); + else + { + Stream stream = dc42ImageFilter.GetDataForkStream(); + stream.Seek((long)(tagOffset + sectorAddress * bptag), SeekOrigin.Begin); + stream.Read(buffer, 0, (int)(length * bptag)); + } + + return buffer; + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + return ReadSectorsLong(sectorAddress, 1); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] data = ReadSectors(sectorAddress, length); + byte[] tags = ReadSectorsTag(sectorAddress, length, SectorTagType.AppleSectorTag); + byte[] buffer = new byte[data.Length + tags.Length]; + + for(uint i = 0; i < length; i++) + { + Array.Copy(data, i * imageInfo.SectorSize, buffer, i * (imageInfo.SectorSize + bptag), + imageInfo.SectorSize); + Array.Copy(tags, i * bptag, buffer, i * (imageInfo.SectorSize + bptag) + imageInfo.SectorSize, bptag); + } + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Structs.cs b/DiscImageChef.DiscImages/DiskCopy42/Structs.cs new file mode 100644 index 000000000..870d13356 --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Structs.cs @@ -0,0 +1,60 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + // DiskCopy 4.2 header, big-endian, data-fork, start of file, 84 bytes + struct Dc42Header + { + /// <summary>0x00, 64 bytes, pascal string, disk name or "-not a Macintosh disk-", filled with garbage</summary> + public string DiskName; + /// <summary>0x40, size of data in bytes (usually sectors*512)</summary> + public uint DataSize; + /// <summary>0x44, size of tags in bytes (usually sectors*12)</summary> + public uint TagSize; + /// <summary>0x48, checksum of data bytes</summary> + public uint DataChecksum; + /// <summary>0x4C, checksum of tag bytes</summary> + public uint TagChecksum; + /// <summary>0x50, format of disk, see constants</summary> + public byte Format; + /// <summary>0x51, format of sectors, see constants</summary> + public byte FmtByte; + /// <summary>0x52, is disk image valid? always 0x01</summary> + public byte Valid; + /// <summary>0x53, reserved, always 0x00</summary> + public byte Reserved; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Unsupported.cs b/DiscImageChef.DiscImages/DiskCopy42/Unsupported.cs new file mode 100644 index 000000000..c06b3ef0c --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Unsupported.cs @@ -0,0 +1,119 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DiskCopy42/Write.cs b/DiscImageChef.DiscImages/DiskCopy42/Write.cs new file mode 100644 index 000000000..59e29b4cf --- /dev/null +++ b/DiscImageChef.DiscImages/DiskCopy42/Write.cs @@ -0,0 +1,403 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Apple DiskCopy 4.2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using Claunia.Encoding; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class DiskCopy42 + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + header = new Dc42Header(); + bool tags = false; + bool macosx = false; + + if(options != null && options.TryGetValue("macosx", out string tmpOption)) + bool.TryParse(tmpOption, out macosx); + + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + switch(mediaType) + { + case MediaType.AppleFileWare: + header.FmtByte = kSigmaFmtByteTwiggy; + header.Format = kSigmaFormatTwiggy; + twiggy = true; + // TODO + ErrorMessage = "Twiggy write support not yet implemented"; + return false; + case MediaType.AppleHD20: + if(sectors != 39040) + { + ErrorMessage = "Incorrect number of sectors for Apple HD20 image"; + return false; + } + + header.FmtByte = kFmtNotStandard; + header.Format = kNotStandardFormat; + tags = true; + break; + case MediaType.AppleProfile: + if(sectors != 9728 && sectors != 19456) + { + ErrorMessage = "Incorrect number of sectors for Apple Profile image"; + return false; + } + + header.FmtByte = kFmtNotStandard; + header.Format = kNotStandardFormat; + tags = true; + break; + case MediaType.AppleSonyDS: + if(sectors != 1600) + { + ErrorMessage = "Incorrect number of sectors for Apple MF2DD image"; + return false; + } + + header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte800K; + header.Format = kSonyFormat800K; + tags = true; + break; + case MediaType.AppleSonySS: + if(sectors != 800) + { + ErrorMessage = "Incorrect number of sectors for Apple MF1DD image"; + return false; + } + + header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte400K; + header.Format = kSonyFormat400K; + tags = true; + break; + case MediaType.AppleWidget: + if(sectors != 39040) + { + ErrorMessage = "Incorrect number of sectors for Apple Widget image"; + return false; + } + + header.FmtByte = kFmtNotStandard; + header.Format = kNotStandardFormat; + tags = true; + break; + case MediaType.DOS_35_DS_DD_9: + if(sectors != 1440) + { + ErrorMessage = "Incorrect number of sectors for MF2DD image"; + return false; + } + + header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte720K; + header.Format = kSonyFormat720K; + break; + case MediaType.DOS_35_HD: + if(sectors != 2880) + { + ErrorMessage = "Incorrect number of sectors for MF2HD image"; + return false; + } + + header.Format = kSonyFmtByte1440K; + header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte1440K; + break; + case MediaType.DMF: + if(sectors != 3360) + { + ErrorMessage = "Incorrect number of sectors for DMF image"; + return false; + } + + header.FmtByte = macosx ? kMacOSXFmtByte : kSonyFmtByte1680K; + header.Format = kSonyFormat1680K; + break; + default: + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + dataOffset = 0x54; + tagOffset = header.TagSize != 0 ? 0x54 + header.DataSize : 0; + header.DiskName = "-DiscImageChef converted image-"; + header.Valid = 1; + header.DataSize = (uint)(sectors * 512); + if(tags) header.TagSize = (uint)(sectors * 12); + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(header.TagSize == 0) + { + ErrorMessage = "Image does not support tags"; + return false; + } + + if(data.Length != 524) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(dataOffset + sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, 512); + writingStream.Seek((long)(tagOffset + sectorAddress * 12), SeekOrigin.Begin); + writingStream.Write(data, 512, 12); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(header.TagSize == 0) + { + ErrorMessage = "Image does not support tags"; + return false; + } + + if(data.Length % 524 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + for(uint i = 0; i < length; i++) + { + writingStream.Seek((long)(dataOffset + (sectorAddress + i) * 512), SeekOrigin.Begin); + writingStream.Write(data, (int)(i * 524 + 0), 512); + writingStream.Seek((long)(tagOffset + (sectorAddress + i) * 12), + SeekOrigin.Begin); + writingStream.Write(data, (int)(i * 524 + 512), + 12); + } + + ErrorMessage = ""; + return true; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + // No tags where written + if(writingStream.Length == 0x54 + header.DataSize) header.TagSize = 0; + + writingStream.Seek(0x54, SeekOrigin.Begin); + byte[] data = new byte[header.DataSize]; + writingStream.Read(data, 0, (int)header.DataSize); + header.DataChecksum = DC42CheckSum(data); + writingStream.Seek(0x54 + header.DataSize, SeekOrigin.Begin); + data = new byte[header.TagSize]; + writingStream.Read(data, 0, (int)header.TagSize); + header.TagChecksum = DC42CheckSum(data); + + writingStream.Seek(0, SeekOrigin.Begin); + if(header.DiskName.Length > 63) header.DiskName = header.DiskName.Substring(0, 63); + writingStream.WriteByte((byte)header.DiskName.Length); + Encoding macRoman = new MacRoman(); + writingStream.Write(macRoman.GetBytes(header.DiskName), 0, header.DiskName.Length); + + writingStream.Seek(64, SeekOrigin.Begin); + writingStream.Write(BigEndianBitConverter.GetBytes(header.DataSize), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(header.TagSize), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(header.DataChecksum), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(header.TagChecksum), 0, 4); + writingStream.WriteByte(header.Format); + writingStream.WriteByte(header.FmtByte); + writingStream.WriteByte(1); + writingStream.WriteByte(0); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + header.DiskName = metadata.MediaTitle ?? "-DiscImageChef converted image-"; + + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy.cs b/DiscImageChef.DiscImages/DriDiskCopy.cs deleted file mode 100644 index 87c709641..000000000 --- a/DiscImageChef.DiscImages/DriDiskCopy.cs +++ /dev/null @@ -1,661 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : DriDiskCopy.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Digital Research's DISKCOPY disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class DriDiskCopy : IWritableImage - { - const string REGEX_DRI = @"DiskImage\s(?<version>\d+.\d+)\s\(C\)\s\d+\,*\d*\s+Digital Research Inc"; - - /// <summary>Disk image file</summary> - IFilter driImageFilter; - - /// <summary>Footer of opened image</summary> - DriFooter footer; - ImageInfo imageInfo; - FileStream writingStream; - - public DriDiskCopy() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Application = "DiskCopy", - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Digital Research DiskCopy"; - public Guid Id => new Guid("9F0BE551-8BAB-4038-8B5A-691F1BF5FFF3"); - - public string Format => "Digital Research DiskCopy"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if((stream.Length - Marshal.SizeOf(typeof(DriFooter))) % 512 != 0) return false; - - byte[] buffer = new byte[Marshal.SizeOf(typeof(DriFooter))]; - stream.Seek(-buffer.Length, SeekOrigin.End); - stream.Read(buffer, 0, buffer.Length); - - IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); - Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); - DriFooter tmpFooter = (DriFooter)Marshal.PtrToStructure(ftrPtr, typeof(DriFooter)); - Marshal.FreeHGlobal(ftrPtr); - - string sig = StringHandlers.CToString(tmpFooter.signature); - - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.signature = \"{0}\"", sig); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.five = {0}", tmpFooter.bpb.five); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.driveCode = {0}", tmpFooter.bpb.driveCode); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown = {0}", tmpFooter.bpb.unknown); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.cylinders = {0}", tmpFooter.bpb.cylinders); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown2 = {0}", tmpFooter.bpb.unknown2); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.bps = {0}", tmpFooter.bpb.bps); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.spc = {0}", tmpFooter.bpb.spc); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.rsectors = {0}", tmpFooter.bpb.rsectors); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.fats_no = {0}", tmpFooter.bpb.fats_no); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.sectors = {0}", tmpFooter.bpb.sectors); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.media_descriptor = {0}", - tmpFooter.bpb.media_descriptor); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.spfat = {0}", tmpFooter.bpb.spfat); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.sptrack = {0}", tmpFooter.bpb.sptrack); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.heads = {0}", tmpFooter.bpb.heads); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.hsectors = {0}", tmpFooter.bpb.hsectors); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.drive_no = {0}", tmpFooter.bpb.drive_no); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown3 = {0}", tmpFooter.bpb.unknown3); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown4 = {0}", tmpFooter.bpb.unknown4); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.sptrack2 = {0}", tmpFooter.bpb.sptrack2); - DicConsole.DebugWriteLine("DRI DiskCopy plugin", - "ArrayHelpers.ArrayIsNullOrEmpty(tmp_footer.bpb.unknown5) = {0}", - ArrayHelpers.ArrayIsNullOrEmpty(tmpFooter.bpb.unknown5)); - - Regex regexSignature = new Regex(REGEX_DRI); - Match matchSignature = regexSignature.Match(sig); - - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "MatchSignature.Success? = {0}", matchSignature.Success); - - if(!matchSignature.Success) return false; - - if(tmpFooter.bpb.sptrack * tmpFooter.bpb.cylinders * tmpFooter.bpb.heads != tmpFooter.bpb.sectors) - return false; - - return tmpFooter.bpb.sectors * tmpFooter.bpb.bps + Marshal.SizeOf(tmpFooter) == stream.Length; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if((stream.Length - Marshal.SizeOf(typeof(DriFooter))) % 512 != 0) return false; - - byte[] buffer = new byte[Marshal.SizeOf(typeof(DriFooter))]; - stream.Seek(-buffer.Length, SeekOrigin.End); - stream.Read(buffer, 0, buffer.Length); - - footer = new DriFooter(); - IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); - Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); - footer = (DriFooter)Marshal.PtrToStructure(ftrPtr, typeof(DriFooter)); - Marshal.FreeHGlobal(ftrPtr); - - string sig = StringHandlers.CToString(footer.signature); - - Regex regexSignature = new Regex(REGEX_DRI); - Match matchSignature = regexSignature.Match(sig); - - if(!matchSignature.Success) return false; - - if(footer.bpb.sptrack * footer.bpb.cylinders * footer.bpb.heads != footer.bpb.sectors) return false; - - if(footer.bpb.sectors * footer.bpb.bps + Marshal.SizeOf(footer) != stream.Length) return false; - - imageInfo.Cylinders = footer.bpb.cylinders; - imageInfo.Heads = footer.bpb.heads; - imageInfo.SectorsPerTrack = footer.bpb.sptrack; - imageInfo.Sectors = footer.bpb.sectors; - imageInfo.SectorSize = footer.bpb.bps; - imageInfo.ApplicationVersion = matchSignature.Groups["version"].Value; - - driImageFilter = imageFilter; - - imageInfo.ImageSize = (ulong)(stream.Length - Marshal.SizeOf(footer)); - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - - DicConsole.DebugWriteLine("DRI DiskCopy plugin", "Image application = {0} version {1}", - imageInfo.Application, imageInfo.ApplicationVersion); - - // Correct some incorrect data in images of NEC 2HD disks - if(imageInfo.Cylinders == 77 && imageInfo.Heads == 2 && imageInfo.SectorsPerTrack == 16 && - imageInfo.SectorSize == 512 && (footer.bpb.driveCode == DriDriveCodes.md2hd || - footer.bpb.driveCode == DriDriveCodes.mf2hd)) - { - imageInfo.SectorsPerTrack = 8; - imageInfo.SectorSize = 1024; - } - - imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, - (ushort)imageInfo.SectorsPerTrack, imageInfo.SectorSize, - MediaEncoding.MFM, false)); - - switch(imageInfo.MediaType) - { - case MediaType.NEC_525_HD when footer.bpb.driveCode == DriDriveCodes.mf2hd || - footer.bpb.driveCode == DriDriveCodes.mf2ed: - imageInfo.MediaType = MediaType.NEC_35_HD_8; - break; - case MediaType.DOS_525_HD when footer.bpb.driveCode == DriDriveCodes.mf2hd || - footer.bpb.driveCode == DriDriveCodes.mf2ed: - imageInfo.MediaType = MediaType.NEC_35_HD_15; - break; - case MediaType.RX50 when footer.bpb.driveCode == DriDriveCodes.md2dd || - footer.bpb.driveCode == DriDriveCodes.md2hd: - imageInfo.MediaType = MediaType.ATARI_35_SS_DD; - break; - } - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - DicConsole.VerboseWriteLine("Digital Research DiskCopy image contains a disk of type {0}", - imageInfo.MediaType); - - return true; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Stream stream = driImageFilter.GetDataForkStream(); - stream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - - return buffer; - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - // TODO: Test with real hardware to see real supported media - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, - MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_SS_DD_11, MediaType.DMF, - MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, - MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, - MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, - MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, - MediaType.FDFORMAT_525_HD, MediaType.RX50, MediaType.XDF_35, MediaType.XDF_525 - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".dsk"}; - - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize == 0) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors > ushort.MaxValue) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - // TODO: Check this - (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool - variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); - - footer = new DriFooter - { - signature = new byte[51], - bpb = new DriBpb - { - five = 5, - driveCode = DriDriveCodes.mf2ed, - cylinders = geometry.cylinders, - bps = (ushort)imageInfo.SectorSize, - sectors = (ushort)imageInfo.Sectors, - sptrack = (ushort)imageInfo.SectorsPerTrack, - heads = (ushort)imageInfo.Heads, - sptrack2 = (ushort)imageInfo.SectorsPerTrack, - unknown5 = new byte[144] - } - }; - Array.Copy(Encoding.ASCII.GetBytes("DiskImage 2.01 (C) 1990,1991 Digital Research Inc"), 0, - footer.signature, 0, 49); - footer.bpbcopy = footer.bpb; - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - byte[] hdr = new byte[Marshal.SizeOf(footer)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(footer)); - Marshal.StructureToPtr(footer, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.End); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is set by media type - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DriFooter - { - /// <summary>Signature: "DiskImage 2.01 (C) 1990,1991 Digital Research Inc\0"</summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 51)] - public byte[] signature; - /// <summary>Information about the disk image, mostly imitates FAT BPB</summary> - public DriBpb bpb; - /// <summary>Information about the disk image, mostly imitates FAT BPB, copy</summary> - public DriBpb bpbcopy; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DriBpb - { - /// <summary>Seems to be always 0x05</summary> - public byte five; - /// <summary>A drive code that corresponds (but it not equal to) CMOS drive types</summary> - public DriDriveCodes driveCode; - /// <summary>Unknown seems to be always 2</summary> - public ushort unknown; - /// <summary>Cylinders</summary> - public ushort cylinders; - /// <summary>Seems to always be 0</summary> - public byte unknown2; - /// <summary>Bytes per sector</summary> - public ushort bps; - /// <summary>Sectors per cluster</summary> - public byte spc; - /// <summary>Sectors between BPB and FAT</summary> - public ushort rsectors; - /// <summary>How many FATs</summary> - public byte fats_no; - /// <summary>Entries in root directory</summary> - public ushort root_entries; - /// <summary>Total sectors</summary> - public ushort sectors; - /// <summary>Media descriptor</summary> - public byte media_descriptor; - /// <summary>Sectors per FAT</summary> - public ushort spfat; - /// <summary>Sectors per track</summary> - public ushort sptrack; - /// <summary>Heads</summary> - public ushort heads; - /// <summary>Hidden sectors before BPB</summary> - public uint hsectors; - /// <summary>Drive number</summary> - public byte drive_no; - /// <summary>Seems to be 0</summary> - public ulong unknown3; - /// <summary>Seems to be 0</summary> - public byte unknown4; - /// <summary>Sectors per track (again?)</summary> - public ushort sptrack2; - /// <summary>Seems to be 0</summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 144)] - public byte[] unknown5; - } - - /// <summary> - /// Drive codes change according to CMOS stored valued - /// </summary> - [SuppressMessage("ReSharper", "InconsistentNaming")] - enum DriDriveCodes : byte - { - /// <summary>5.25" 360k</summary> - md2dd = 0, - /// <summary>5.25" 1.2M</summary> - md2hd = 1, - /// <summary>3.5" 720k</summary> - mf2dd = 2, - /// <summary>3.5" 1.44M</summary> - mf2hd = 7, - /// <summary>3.5" 2.88M</summary> - mf2ed = 9 - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Constants.cs b/DiscImageChef.DiscImages/DriDiskCopy/Constants.cs new file mode 100644 index 000000000..a38b22e1e --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Constants.cs @@ -0,0 +1,39 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + const string REGEX_DRI = @"DiskImage\s(?<version>\d+.\d+)\s\(C\)\s\d+\,*\d*\s+Digital Research Inc"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/DriDiskCopy.cs b/DiscImageChef.DiscImages/DriDiskCopy/DriDiskCopy.cs new file mode 100644 index 000000000..850cc1d06 --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/DriDiskCopy.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : DriDiskCopy.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy : IWritableImage + { + /// <summary>Disk image file</summary> + IFilter driImageFilter; + + /// <summary>Footer of opened image</summary> + DriFooter footer; + ImageInfo imageInfo; + FileStream writingStream; + + public DriDiskCopy() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Application = "DiskCopy", + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Enums.cs b/DiscImageChef.DiscImages/DriDiskCopy/Enums.cs new file mode 100644 index 000000000..c86909ca5 --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Enums.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics.CodeAnalysis; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + /// <summary> + /// Drive codes change according to CMOS stored valued + /// </summary> + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum DriDriveCodes : byte + { + /// <summary>5.25" 360k</summary> + md2dd = 0, + /// <summary>5.25" 1.2M</summary> + md2hd = 1, + /// <summary>3.5" 720k</summary> + mf2dd = 2, + /// <summary>3.5" 1.44M</summary> + mf2hd = 7, + /// <summary>3.5" 2.88M</summary> + mf2ed = 9 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Identify.cs b/DiscImageChef.DiscImages/DriDiskCopy/Identify.cs new file mode 100644 index 000000000..fcb0dd1b3 --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Identify.cs @@ -0,0 +1,99 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if((stream.Length - Marshal.SizeOf(typeof(DriFooter))) % 512 != 0) return false; + + byte[] buffer = new byte[Marshal.SizeOf(typeof(DriFooter))]; + stream.Seek(-buffer.Length, SeekOrigin.End); + stream.Read(buffer, 0, buffer.Length); + + IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); + DriFooter tmpFooter = (DriFooter)Marshal.PtrToStructure(ftrPtr, typeof(DriFooter)); + Marshal.FreeHGlobal(ftrPtr); + + string sig = StringHandlers.CToString(tmpFooter.signature); + + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.signature = \"{0}\"", sig); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.five = {0}", tmpFooter.bpb.five); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.driveCode = {0}", tmpFooter.bpb.driveCode); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown = {0}", tmpFooter.bpb.unknown); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.cylinders = {0}", tmpFooter.bpb.cylinders); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown2 = {0}", tmpFooter.bpb.unknown2); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.bps = {0}", tmpFooter.bpb.bps); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.spc = {0}", tmpFooter.bpb.spc); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.rsectors = {0}", tmpFooter.bpb.rsectors); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.fats_no = {0}", tmpFooter.bpb.fats_no); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.sectors = {0}", tmpFooter.bpb.sectors); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.media_descriptor = {0}", + tmpFooter.bpb.media_descriptor); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.spfat = {0}", tmpFooter.bpb.spfat); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.sptrack = {0}", tmpFooter.bpb.sptrack); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.heads = {0}", tmpFooter.bpb.heads); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.hsectors = {0}", tmpFooter.bpb.hsectors); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.drive_no = {0}", tmpFooter.bpb.drive_no); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown3 = {0}", tmpFooter.bpb.unknown3); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.unknown4 = {0}", tmpFooter.bpb.unknown4); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "tmp_footer.bpb.sptrack2 = {0}", tmpFooter.bpb.sptrack2); + DicConsole.DebugWriteLine("DRI DiskCopy plugin", + "ArrayHelpers.ArrayIsNullOrEmpty(tmp_footer.bpb.unknown5) = {0}", + ArrayHelpers.ArrayIsNullOrEmpty(tmpFooter.bpb.unknown5)); + + Regex regexSignature = new Regex(REGEX_DRI); + Match matchSignature = regexSignature.Match(sig); + + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "MatchSignature.Success? = {0}", matchSignature.Success); + + if(!matchSignature.Success) return false; + + if(tmpFooter.bpb.sptrack * tmpFooter.bpb.cylinders * tmpFooter.bpb.heads != tmpFooter.bpb.sectors) + return false; + + return tmpFooter.bpb.sectors * tmpFooter.bpb.bps + Marshal.SizeOf(tmpFooter) == stream.Length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Properties.cs b/DiscImageChef.DiscImages/DriDiskCopy/Properties.cs new file mode 100644 index 000000000..cfc87714d --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Properties.cs @@ -0,0 +1,82 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + public ImageInfo Info => imageInfo; + + public string Name => "Digital Research DiskCopy"; + public Guid Id => new Guid("9F0BE551-8BAB-4038-8B5A-691F1BF5FFF3"); + + public string Format => "Digital Research DiskCopy"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + // TODO: Test with real hardware to see real supported media + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, + MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_SS_DD_11, MediaType.DMF, + MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, + MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, + MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, + MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, + MediaType.FDFORMAT_525_HD, MediaType.RX50, MediaType.XDF_35, MediaType.XDF_525 + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".dsk"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Read.cs b/DiscImageChef.DiscImages/DriDiskCopy/Read.cs new file mode 100644 index 000000000..03e51057d --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Read.cs @@ -0,0 +1,147 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if((stream.Length - Marshal.SizeOf(typeof(DriFooter))) % 512 != 0) return false; + + byte[] buffer = new byte[Marshal.SizeOf(typeof(DriFooter))]; + stream.Seek(-buffer.Length, SeekOrigin.End); + stream.Read(buffer, 0, buffer.Length); + + footer = new DriFooter(); + IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); + footer = (DriFooter)Marshal.PtrToStructure(ftrPtr, typeof(DriFooter)); + Marshal.FreeHGlobal(ftrPtr); + + string sig = StringHandlers.CToString(footer.signature); + + Regex regexSignature = new Regex(REGEX_DRI); + Match matchSignature = regexSignature.Match(sig); + + if(!matchSignature.Success) return false; + + if(footer.bpb.sptrack * footer.bpb.cylinders * footer.bpb.heads != footer.bpb.sectors) return false; + + if(footer.bpb.sectors * footer.bpb.bps + Marshal.SizeOf(footer) != stream.Length) return false; + + imageInfo.Cylinders = footer.bpb.cylinders; + imageInfo.Heads = footer.bpb.heads; + imageInfo.SectorsPerTrack = footer.bpb.sptrack; + imageInfo.Sectors = footer.bpb.sectors; + imageInfo.SectorSize = footer.bpb.bps; + imageInfo.ApplicationVersion = matchSignature.Groups["version"].Value; + + driImageFilter = imageFilter; + + imageInfo.ImageSize = (ulong)(stream.Length - Marshal.SizeOf(footer)); + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + + DicConsole.DebugWriteLine("DRI DiskCopy plugin", "Image application = {0} version {1}", + imageInfo.Application, imageInfo.ApplicationVersion); + + // Correct some incorrect data in images of NEC 2HD disks + if(imageInfo.Cylinders == 77 && imageInfo.Heads == 2 && imageInfo.SectorsPerTrack == 16 && + imageInfo.SectorSize == 512 && (footer.bpb.driveCode == DriDriveCodes.md2hd || + footer.bpb.driveCode == DriDriveCodes.mf2hd)) + { + imageInfo.SectorsPerTrack = 8; + imageInfo.SectorSize = 1024; + } + + imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, + (ushort)imageInfo.SectorsPerTrack, imageInfo.SectorSize, + MediaEncoding.MFM, false)); + + switch(imageInfo.MediaType) + { + case MediaType.NEC_525_HD when footer.bpb.driveCode == DriDriveCodes.mf2hd || + footer.bpb.driveCode == DriDriveCodes.mf2ed: + imageInfo.MediaType = MediaType.NEC_35_HD_8; + break; + case MediaType.DOS_525_HD when footer.bpb.driveCode == DriDriveCodes.mf2hd || + footer.bpb.driveCode == DriDriveCodes.mf2ed: + imageInfo.MediaType = MediaType.NEC_35_HD_15; + break; + case MediaType.RX50 when footer.bpb.driveCode == DriDriveCodes.md2dd || + footer.bpb.driveCode == DriDriveCodes.md2hd: + imageInfo.MediaType = MediaType.ATARI_35_SS_DD; + break; + } + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + DicConsole.VerboseWriteLine("Digital Research DiskCopy image contains a disk of type {0}", + imageInfo.MediaType); + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Stream stream = driImageFilter.GetDataForkStream(); + stream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Structs.cs b/DiscImageChef.DiscImages/DriDiskCopy/Structs.cs new file mode 100644 index 000000000..3a3f3c94f --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Structs.cs @@ -0,0 +1,99 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DriFooter + { + /// <summary>Signature: "DiskImage 2.01 (C) 1990,1991 Digital Research Inc\0"</summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 51)] + public byte[] signature; + /// <summary>Information about the disk image, mostly imitates FAT BPB</summary> + public DriBpb bpb; + /// <summary>Information about the disk image, mostly imitates FAT BPB, copy</summary> + public DriBpb bpbcopy; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DriBpb + { + /// <summary>Seems to be always 0x05</summary> + public byte five; + /// <summary>A drive code that corresponds (but it not equal to) CMOS drive types</summary> + public DriDriveCodes driveCode; + /// <summary>Unknown seems to be always 2</summary> + public ushort unknown; + /// <summary>Cylinders</summary> + public ushort cylinders; + /// <summary>Seems to always be 0</summary> + public byte unknown2; + /// <summary>Bytes per sector</summary> + public ushort bps; + /// <summary>Sectors per cluster</summary> + public byte spc; + /// <summary>Sectors between BPB and FAT</summary> + public ushort rsectors; + /// <summary>How many FATs</summary> + public byte fats_no; + /// <summary>Entries in root directory</summary> + public ushort root_entries; + /// <summary>Total sectors</summary> + public ushort sectors; + /// <summary>Media descriptor</summary> + public byte media_descriptor; + /// <summary>Sectors per FAT</summary> + public ushort spfat; + /// <summary>Sectors per track</summary> + public ushort sptrack; + /// <summary>Heads</summary> + public ushort heads; + /// <summary>Hidden sectors before BPB</summary> + public uint hsectors; + /// <summary>Drive number</summary> + public byte drive_no; + /// <summary>Seems to be 0</summary> + public ulong unknown3; + /// <summary>Seems to be 0</summary> + public byte unknown4; + /// <summary>Sectors per track (again?)</summary> + public ushort sptrack2; + /// <summary>Seems to be 0</summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 144)] + public byte[] unknown5; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Unsupported.cs b/DiscImageChef.DiscImages/DriDiskCopy/Unsupported.cs new file mode 100644 index 000000000..f0a497e9a --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Unsupported.cs @@ -0,0 +1,144 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/DriDiskCopy/Write.cs b/DiscImageChef.DiscImages/DriDiskCopy/Write.cs new file mode 100644 index 000000000..6c8b7d574 --- /dev/null +++ b/DiscImageChef.DiscImages/DriDiskCopy/Write.cs @@ -0,0 +1,245 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Digital Research's DISKCOPY disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class DriDiskCopy + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize == 0) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors > ushort.MaxValue) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + // TODO: Check this + (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool + variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); + + footer = new DriFooter + { + signature = new byte[51], + bpb = new DriBpb + { + five = 5, + driveCode = DriDriveCodes.mf2ed, + cylinders = geometry.cylinders, + bps = (ushort)imageInfo.SectorSize, + sectors = (ushort)imageInfo.Sectors, + sptrack = (ushort)imageInfo.SectorsPerTrack, + heads = (ushort)imageInfo.Heads, + sptrack2 = (ushort)imageInfo.SectorsPerTrack, + unknown5 = new byte[144] + } + }; + Array.Copy(Encoding.ASCII.GetBytes("DiskImage 2.01 (C) 1990,1991 Digital Research Inc"), 0, + footer.signature, 0, 49); + footer.bpbcopy = footer.bpb; + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + byte[] hdr = new byte[Marshal.SizeOf(footer)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(footer)); + Marshal.StructureToPtr(footer, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.End); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is set by media type + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/GDI/Constants.cs b/DiscImageChef.DiscImages/GDI/Constants.cs new file mode 100644 index 000000000..bc927f610 --- /dev/null +++ b/DiscImageChef.DiscImages/GDI/Constants.cs @@ -0,0 +1,42 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Dreamcast GDI disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Gdi + { + const string REGEX_TRACK = + @"\s?(?<track>\d+)\s+(?<start>\d+)\s(?<flags>\d)\s(?<type>2352|2048)\s(?<filename>.+)\s(?<offset>\d+)$"; + + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/GDI/GDI.cs b/DiscImageChef.DiscImages/GDI/GDI.cs new file mode 100644 index 000000000..123e5a799 --- /dev/null +++ b/DiscImageChef.DiscImages/GDI/GDI.cs @@ -0,0 +1,94 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : GDI.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Dreamcast GDI disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Console; +using DiscImageChef.Filters; +using Schemas; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + // TODO: There seems no be no clear definition on how to treat pregaps that are not included in the file, so this is just appending it to start of track + // TODO: This format doesn't support to specify pregaps that are included in the file (like Redump ones) + public partial class Gdi : IMediaImage + { + ulong densitySeparationSectors; + GdiDisc discimage; + StreamReader gdiStream; + ImageInfo imageInfo; + Stream imageStream; + /// <summary>Dictionary, index is track #, value is track number, or 0 if a TOC</summary> + Dictionary<uint, ulong> offsetmap; + + public Gdi() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = true, + HasSessions = true, + Version = null, + ApplicationVersion = null, + MediaTitle = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + + + + + + + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/GDI/Identify.cs b/DiscImageChef.DiscImages/GDI/Identify.cs new file mode 100644 index 000000000..21c28585a --- /dev/null +++ b/DiscImageChef.DiscImages/GDI/Identify.cs @@ -0,0 +1,110 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Dreamcast GDI disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Gdi + { + // Due to .gdi format, this method must parse whole file, ignoring errors (those will be thrown by OpenImage()). + public bool Identify(IFilter imageFilter) + { + try + { + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + byte[] testArray = new byte[512]; + imageFilter.GetDataForkStream().Read(testArray, 0, 512); + imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); + // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin + bool twoConsecutiveNulls = false; + for(int i = 0; i < 512; i++) + { + if(i >= imageFilter.GetDataForkStream().Length) break; + + if(testArray[i] == 0) + { + if(twoConsecutiveNulls) return false; + + twoConsecutiveNulls = true; + } + else twoConsecutiveNulls = false; + + if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) + return false; + } + + gdiStream = new StreamReader(imageFilter.GetDataForkStream()); + int lineNumber = 0; + int tracksFound = 0; + int tracks = 0; + + while(gdiStream.Peek() >= 0) + { + lineNumber++; + string line = gdiStream.ReadLine(); + + if(lineNumber == 1) + { + if(!int.TryParse(line, out tracks)) return false; + } + else + { + Regex regexTrack = new Regex(REGEX_TRACK); + + Match trackMatch = regexTrack.Match(line ?? throw new InvalidOperationException()); + + if(!trackMatch.Success) return false; + + tracksFound++; + } + } + + if(tracks == 0) return false; + + return tracks == tracksFound; + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", imageFilter.GetBasePath()); + DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); + DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); + return false; + } + } + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/GDI/Properties.cs b/DiscImageChef.DiscImages/GDI/Properties.cs new file mode 100644 index 000000000..bf1591804 --- /dev/null +++ b/DiscImageChef.DiscImages/GDI/Properties.cs @@ -0,0 +1,93 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Dreamcast GDI disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + public partial class Gdi + { + public string Name => "Dreamcast GDI image"; + public Guid Id => new Guid("281ECBF2-D2A7-414C-8497-1A33F6DCB2DD"); + public ImageInfo Info => imageInfo; + + public string Format => "Dreamcast GDI image"; + + public List<Partition> Partitions { get; private set; } + + public List<Track> Tracks + { + get + { + List<Track> tracks = new List<Track>(); + + foreach(GdiTrack gdiTrack in discimage.Tracks) + { + Track track = new Track + { + Indexes = new Dictionary<int, ulong>(), + TrackDescription = null, + TrackStartSector = gdiTrack.StartSector, + TrackPregap = gdiTrack.Pregap, + TrackSession = (ushort)(gdiTrack.HighDensity ? 2 : 1), + TrackSequence = gdiTrack.Sequence, + TrackType = gdiTrack.Tracktype, + TrackFilter = gdiTrack.Trackfilter, + TrackFile = gdiTrack.Trackfile, + TrackFileOffset = (ulong)gdiTrack.Offset, + TrackFileType = "BINARY", + TrackRawBytesPerSector = gdiTrack.Bps, + TrackBytesPerSector = gdiTrack.Tracktype == TrackType.Data ? 2048 : 2352, + TrackSubchannelType = TrackSubchannelType.None + }; + + track.TrackEndSector = track.TrackStartSector + gdiTrack.Sectors - 1; + + tracks.Add(track); + } + + return tracks; + } + } + + public List<Session> Sessions => discimage.Sessions; + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/GDI.cs b/DiscImageChef.DiscImages/GDI/Read.cs similarity index 82% rename from DiscImageChef.DiscImages/GDI.cs rename to DiscImageChef.DiscImages/GDI/Read.cs index b0987baab..ea3e8ac0e 100644 --- a/DiscImageChef.DiscImages/GDI.cs +++ b/DiscImageChef.DiscImages/GDI/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : GDI.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // -// Component : Disc image plugins. +// Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Dreamcast GDI disc images. +// Reads Dreamcast GDI disc images. // // --[ License ] -------------------------------------------------------------- // @@ -42,163 +42,12 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - // TODO: There seems no be no clear definition on how to treat pregaps that are not included in the file, so this is just appending it to start of track - // TODO: This format doesn't support to specify pregaps that are included in the file (like Redump ones) - public class Gdi : IMediaImage + public partial class Gdi { - const string REGEX_TRACK = - @"\s?(?<track>\d+)\s+(?<start>\d+)\s(?<flags>\d)\s(?<type>2352|2048)\s(?<filename>.+)\s(?<offset>\d+)$"; - - ulong densitySeparationSectors; - GdiDisc discimage; - StreamReader gdiStream; - ImageInfo imageInfo; - Stream imageStream; - /// <summary>Dictionary, index is track #, value is track number, or 0 if a TOC</summary> - Dictionary<uint, ulong> offsetmap; - - public Gdi() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = true, - HasSessions = true, - Version = null, - ApplicationVersion = null, - MediaTitle = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "Dreamcast GDI image"; - public Guid Id => new Guid("281ECBF2-D2A7-414C-8497-1A33F6DCB2DD"); - public ImageInfo Info => imageInfo; - - public string Format => "Dreamcast GDI image"; - - public List<Partition> Partitions { get; private set; } - - public List<Track> Tracks - { - get - { - List<Track> tracks = new List<Track>(); - - foreach(GdiTrack gdiTrack in discimage.Tracks) - { - Track track = new Track - { - Indexes = new Dictionary<int, ulong>(), - TrackDescription = null, - TrackStartSector = gdiTrack.StartSector, - TrackPregap = gdiTrack.Pregap, - TrackSession = (ushort)(gdiTrack.HighDensity ? 2 : 1), - TrackSequence = gdiTrack.Sequence, - TrackType = gdiTrack.Tracktype, - TrackFilter = gdiTrack.Trackfilter, - TrackFile = gdiTrack.Trackfile, - TrackFileOffset = (ulong)gdiTrack.Offset, - TrackFileType = "BINARY", - TrackRawBytesPerSector = gdiTrack.Bps, - TrackBytesPerSector = gdiTrack.Tracktype == TrackType.Data ? 2048 : 2352, - TrackSubchannelType = TrackSubchannelType.None - }; - - track.TrackEndSector = track.TrackStartSector + gdiTrack.Sectors - 1; - - tracks.Add(track); - } - - return tracks; - } - } - - public List<Session> Sessions => discimage.Sessions; - - // Due to .gdi format, this method must parse whole file, ignoring errors (those will be thrown by OpenImage()). - public bool Identify(IFilter imageFilter) - { - try - { - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - byte[] testArray = new byte[512]; - imageFilter.GetDataForkStream().Read(testArray, 0, 512); - imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); - // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin - bool twoConsecutiveNulls = false; - for(int i = 0; i < 512; i++) - { - if(i >= imageFilter.GetDataForkStream().Length) break; - - if(testArray[i] == 0) - { - if(twoConsecutiveNulls) return false; - - twoConsecutiveNulls = true; - } - else twoConsecutiveNulls = false; - - if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) - return false; - } - - gdiStream = new StreamReader(imageFilter.GetDataForkStream()); - int lineNumber = 0; - int tracksFound = 0; - int tracks = 0; - - while(gdiStream.Peek() >= 0) - { - lineNumber++; - string line = gdiStream.ReadLine(); - - if(lineNumber == 1) - { - if(!int.TryParse(line, out tracks)) return false; - } - else - { - Regex regexTrack = new Regex(REGEX_TRACK); - - Match trackMatch = regexTrack.Match(line ?? throw new InvalidOperationException()); - - if(!trackMatch.Success) return false; - - tracksFound++; - } - } - - if(tracks == 0) return false; - - return tracks == tracksFound; - } - catch(Exception ex) - { - DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", imageFilter.GetBasePath()); - DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); - DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); - return false; - } - } - - public bool Open(IFilter imageFilter) + public bool Open(IFilter imageFilter) { if(imageFilter == null) return false; @@ -441,11 +290,6 @@ namespace DiscImageChef.DiscImages } } - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureSupportedButNotImplementedImageException("Feature not supported by image format"); - } - public byte[] ReadSector(ulong sectorAddress) { return ReadSectors(sectorAddress, 1); @@ -1009,44 +853,5 @@ namespace DiscImageChef.DiscImages { return null; } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - struct GdiTrack - { - /// <summary>Track #</summary> - public uint Sequence; - /// <summary>Track filter</summary> - public IFilter Trackfilter; - /// <summary>Track file</summary> - public string Trackfile; - /// <summary>Track byte offset in file</summary> - public long Offset; - /// <summary>Track flags</summary> - public byte Flags; - /// <summary>Track starting sector</summary> - public ulong StartSector; - /// <summary>Bytes per sector</summary> - public ushort Bps; - /// <summary>Sectors in track</summary> - public ulong Sectors; - /// <summary>Track type</summary> - public TrackType Tracktype; - /// <summary>Track session</summary> - public bool HighDensity; - /// <summary>Pregap sectors not stored in track file</summary> - public ulong Pregap; - } - - struct GdiDisc - { - /// <summary>Sessions</summary> - public List<Session> Sessions; - /// <summary>Tracks</summary> - public List<GdiTrack> Tracks; - /// <summary>Disk type</summary> - public MediaType Disktype; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/GDI/Structs.cs b/DiscImageChef.DiscImages/GDI/Structs.cs new file mode 100644 index 000000000..ca9e480c0 --- /dev/null +++ b/DiscImageChef.DiscImages/GDI/Structs.cs @@ -0,0 +1,80 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Dreamcast GDI disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Gdi + { + struct GdiTrack + { + /// <summary>Track #</summary> + public uint Sequence; + /// <summary>Track filter</summary> + public IFilter Trackfilter; + /// <summary>Track file</summary> + public string Trackfile; + /// <summary>Track byte offset in file</summary> + public long Offset; + /// <summary>Track flags</summary> + public byte Flags; + /// <summary>Track starting sector</summary> + public ulong StartSector; + /// <summary>Bytes per sector</summary> + public ushort Bps; + /// <summary>Sectors in track</summary> + public ulong Sectors; + /// <summary>Track type</summary> + public TrackType Tracktype; + /// <summary>Track session</summary> + public bool HighDensity; + /// <summary>Pregap sectors not stored in track file</summary> + public ulong Pregap; + } + + struct GdiDisc + { + /// <summary>Sessions</summary> + public List<Session> Sessions; + /// <summary>Tracks</summary> + public List<GdiTrack> Tracks; + /// <summary>Disk type</summary> + public MediaType Disktype; + } + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/GDI/Unsupported.cs b/DiscImageChef.DiscImages/GDI/Unsupported.cs new file mode 100644 index 000000000..619b9903e --- /dev/null +++ b/DiscImageChef.DiscImages/GDI/Unsupported.cs @@ -0,0 +1,46 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Dreamcast GDI disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; + +namespace DiscImageChef.DiscImages +{ + public partial class Gdi + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureSupportedButNotImplementedImageException("Feature not supported by image format"); + } + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy.cs b/DiscImageChef.DiscImages/HDCopy.cs deleted file mode 100644 index c56e0dcb0..000000000 --- a/DiscImageChef.DiscImages/HDCopy.cs +++ /dev/null @@ -1,471 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : HDCopy.cs -// Author(s) : Michael Drüing <michael@drueing.de> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages floppy disk images created with HD-Copy -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2017 Michael Drüing -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -/* Some information on the file format from Michal Necasek (www.os2museum.com): - * - * The HD-Copy diskette image format was used by the eponymous DOS utility, - * written by Oliver Fromme around 1995. The HD-Copy format is relatively - * straightforward, supporting images with 512-byte sector size and uniform - * sectors per track count. A basic form of run-length compression is also - * supported, and empty/unused tracks aren't stored in the image. Images - * with up to 82 cylinders are supported. - * - * No provision appears to be made for single-sided images. The disk image - * is stored as a sequence of compressed tracks (where a track refers to only - * one side of the disk), and individual tracks may be left out. - * - * The HD-Copy RLE compression works as follows. The image is divided into a - * number of independent blocks, one per track. Each compressed block starts - * with a header which contains the size of compressed data (16-bit little - * endian) and the escape byte. Whenever the escape byte is encountered in the - * byte stream, it is followed by a data byte and a count byte. - * - * Note that HD-Copy uses RLE compression for sequences of as few as three - * bytes, even though that provides no benefit. - * - * It would be tempting to perform in-place decompression to save memory. - * Unfortunately the simplistic RLE algorithm means the encoded data may be - * larger than the decoded version, with unknown worst case behavior. Hence - * the compressed data for a sector may not fit into a buffer the size of the - * uncompressed sector. - * - * There is no signature, hence heuristics must be used to identify a HD-Copy - * diskette image. Fortunately, the HD-Copy header is highly recognizable. - */ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class HdCopy : IMediaImage - { - /// <summary> - /// The HDCP file header after the image has been opened - /// </summary> - HdcpFileHeader fileHeader; - - /// <summary> - /// The ImageFilter we're reading from, after the file has been opened - /// </summary> - IFilter hdcpImageFilter; - ImageInfo imageInfo; - - /// <summary> - /// Every track that has been read is cached here - /// </summary> - Dictionary<int, byte[]> trackCache = new Dictionary<int, byte[]>(); - - /// <summary> - /// The offset in the file where each track starts, or -1 if the track is not present - /// </summary> - Dictionary<int, long> trackOffset = new Dictionary<int, long>(); - - public HdCopy() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "HD-Copy disk image"; - public Guid Id => new Guid("8D57483F-71A5-42EC-9B87-66AEC439C792"); - - public string Format => "HD-Copy image"; - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 2 + 2 * 82) return false; - - byte[] header = new byte[2 + 2 * 82]; - stream.Read(header, 0, 2 + 2 * 82); - - IntPtr hdrPtr = Marshal.AllocHGlobal(2 + 2 * 82); - Marshal.Copy(header, 0, hdrPtr, 2 + 2 * 82); - HdcpFileHeader fheader = (HdcpFileHeader)Marshal.PtrToStructure(hdrPtr, typeof(HdcpFileHeader)); - Marshal.FreeHGlobal(hdrPtr); - - /* Some sanity checks on the values we just read. - * We know the image is from a DOS floppy disk, so assume - * some sane cylinder and sectors-per-track count. - */ - if(fheader.sectorsPerTrack < 8 || fheader.sectorsPerTrack > 40) return false; - - if(fheader.lastCylinder < 37 || fheader.lastCylinder >= 82) return false; - - // Validate the trackmap. First two tracks need to be present - if(fheader.trackMap[0] != 1 || fheader.trackMap[1] != 1) return false; - - // all other tracks must be either present (=1) or absent (=0) - for(int i = 0; i < 2 * 82; i++) - if(fheader.trackMap[i] > 1) - return false; - - // TODO: validate the tracks - // For now, having a valid header should be sufficient. - return true; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - byte[] header = new byte[2 + 2 * 82]; - stream.Read(header, 0, 2 + 2 * 82); - - IntPtr hdrPtr = Marshal.AllocHGlobal(2 + 2 * 82); - Marshal.Copy(header, 0, hdrPtr, 2 + 2 * 82); - HdcpFileHeader fheader = (HdcpFileHeader)Marshal.PtrToStructure(hdrPtr, typeof(HdcpFileHeader)); - Marshal.FreeHGlobal(hdrPtr); - DicConsole.DebugWriteLine("HDCP plugin", - "Detected HD-Copy image with {0} tracks and {1} sectors per track.", - fheader.lastCylinder + 1, fheader.sectorsPerTrack); - - imageInfo.Cylinders = (uint)fheader.lastCylinder + 1; - imageInfo.SectorsPerTrack = fheader.sectorsPerTrack; - imageInfo.SectorSize = 512; // only 512 bytes per sector supported - imageInfo.Heads = 2; // only 2-sided floppies are supported - imageInfo.Sectors = 2 * imageInfo.Cylinders * imageInfo.SectorsPerTrack; - imageInfo.ImageSize = imageInfo.Sectors * imageInfo.SectorSize; - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, 2, - (ushort)imageInfo.SectorsPerTrack, 512, MediaEncoding.MFM, - false)); - - // the start offset of the track data - long currentOffset = 2 + 2 * 82; - - // build table of track offsets - for(int i = 0; i < imageInfo.Cylinders * 2; i++) - if(fheader.trackMap[i] == 0) - trackOffset[i] = -1; - else - { - // track is present, read the block header - if(currentOffset + 3 >= stream.Length) return false; - - byte[] blkHeader = new byte[2]; - stream.Read(blkHeader, 0, 2); - short blkLength = BitConverter.ToInt16(blkHeader, 0); - - // assume block sizes are positive - if(blkLength < 0) return false; - - DicConsole.DebugWriteLine("HDCP plugin", "Track {0} offset 0x{1:x8}, size={2:x4}", i, currentOffset, - blkLength); - trackOffset[i] = currentOffset; - - currentOffset += 2 + blkLength; - // skip the block data - stream.Seek(blkLength, SeekOrigin.Current); - } - - // ensure that the last track is present completely - if(currentOffset > stream.Length) return false; - - // save some variables for later use - fileHeader = fheader; - hdcpImageFilter = imageFilter; - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - int trackNum = (int)(sectorAddress / imageInfo.SectorsPerTrack); - int sectorOffset = (int)(sectorAddress % imageInfo.SectorsPerTrack); - - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(trackNum > 2 * imageInfo.Cylinders) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - byte[] result = new byte[imageInfo.SectorSize]; - if(trackOffset[trackNum] == -1) Array.Clear(result, 0, (int)imageInfo.SectorSize); - else - { - // track is present in file, make sure it has been loaded - if(!trackCache.ContainsKey(trackNum)) ReadTrackIntoCache(hdcpImageFilter.GetDataForkStream(), trackNum); - - Array.Copy(trackCache[trackNum], sectorOffset * imageInfo.SectorSize, result, 0, imageInfo.SectorSize); - } - - return result; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - byte[] result = new byte[length * imageInfo.SectorSize]; - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - for(int i = 0; i < length; i++) - ReadSector(sectorAddress + (ulong)i).CopyTo(result, i * imageInfo.SectorSize); - - return result; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - void ReadTrackIntoCache(Stream stream, int tracknum) - { - byte[] trackData = new byte[imageInfo.SectorSize * imageInfo.SectorsPerTrack]; - byte[] blkHeader = new byte[3]; - - // check that track is present - if(trackOffset[tracknum] == -1) - throw new InvalidDataException("Tried reading a track that is not present in image"); - - stream.Seek(trackOffset[tracknum], SeekOrigin.Begin); - - // read the compressed track data - stream.Read(blkHeader, 0, 3); - short compressedLength = (short)(BitConverter.ToInt16(blkHeader, 0) - 1); - byte escapeByte = blkHeader[2]; - - byte[] cBuffer = new byte[compressedLength]; - stream.Read(cBuffer, 0, compressedLength); - - // decompress the data - int sIndex = 0; // source buffer position - int dIndex = 0; // destination buffer position - while(sIndex < compressedLength) - if(cBuffer[sIndex] == escapeByte) - { - sIndex++; // skip over escape byte - byte fillByte = cBuffer[sIndex++]; - byte fillCount = cBuffer[sIndex++]; - // fill destination buffer - for(int i = 0; i < fillCount; i++) trackData[dIndex++] = fillByte; - } - else - trackData[dIndex++] = cBuffer[sIndex++]; - - // check that the number of bytes decompressed matches a whole track - if(dIndex != imageInfo.SectorSize * imageInfo.SectorsPerTrack) - throw new InvalidDataException("Track decompression yielded incomplete data"); - - // store track in cache - trackCache[tracknum] = trackData; - } - - /// <summary> - /// The global header of a HDCP image file - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct HdcpFileHeader - { - /// <summary> - /// Last cylinder (zero-based) - /// </summary> - public byte lastCylinder; - - /// <summary> - /// Sectors per track - /// </summary> - public byte sectorsPerTrack; - - /// <summary> - /// The track map. It contains one byte for each track. - /// Up to 82 tracks (41 tracks * 2 sides) are supported. - /// 0 means track is not present, 1 means it is present. - /// The first 2 tracks are always present. - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2 * 82)] - public byte[] trackMap; - } - - /// <summary> - /// The header for a RLE-compressed block - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct HdcpBlockHeader - { - /// <summary> - /// The length of the compressed block, in bytes. Little-endian. - /// </summary> - public ushort length; - - /// <summary> - /// The byte value used as RLE escape sequence - /// </summary> - public byte escape; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy/HDCopy.cs b/DiscImageChef.DiscImages/HDCopy/HDCopy.cs new file mode 100644 index 000000000..2cfd1b8c7 --- /dev/null +++ b/DiscImageChef.DiscImages/HDCopy/HDCopy.cs @@ -0,0 +1,123 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : HDCopy.cs +// Author(s) : Michael Drüing <michael@drueing.de> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages floppy disk images created with HD-Copy +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2017 Michael Drüing +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +/* Some information on the file format from Michal Necasek (www.os2museum.com): + * + * The HD-Copy diskette image format was used by the eponymous DOS utility, + * written by Oliver Fromme around 1995. The HD-Copy format is relatively + * straightforward, supporting images with 512-byte sector size and uniform + * sectors per track count. A basic form of run-length compression is also + * supported, and empty/unused tracks aren't stored in the image. Images + * with up to 82 cylinders are supported. + * + * No provision appears to be made for single-sided images. The disk image + * is stored as a sequence of compressed tracks (where a track refers to only + * one side of the disk), and individual tracks may be left out. + * + * The HD-Copy RLE compression works as follows. The image is divided into a + * number of independent blocks, one per track. Each compressed block starts + * with a header which contains the size of compressed data (16-bit little + * endian) and the escape byte. Whenever the escape byte is encountered in the + * byte stream, it is followed by a data byte and a count byte. + * + * Note that HD-Copy uses RLE compression for sequences of as few as three + * bytes, even though that provides no benefit. + * + * It would be tempting to perform in-place decompression to save memory. + * Unfortunately the simplistic RLE algorithm means the encoded data may be + * larger than the decoded version, with unknown worst case behavior. Hence + * the compressed data for a sector may not fit into a buffer the size of the + * uncompressed sector. + * + * There is no signature, hence heuristics must be used to identify a HD-Copy + * diskette image. Fortunately, the HD-Copy header is highly recognizable. + */ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class HdCopy : IMediaImage + { + /// <summary> + /// The HDCP file header after the image has been opened + /// </summary> + HdcpFileHeader fileHeader; + + /// <summary> + /// The ImageFilter we're reading from, after the file has been opened + /// </summary> + IFilter hdcpImageFilter; + ImageInfo imageInfo; + + /// <summary> + /// Every track that has been read is cached here + /// </summary> + Dictionary<int, byte[]> trackCache = new Dictionary<int, byte[]>(); + + /// <summary> + /// The offset in the file where each track starts, or -1 if the track is not present + /// </summary> + Dictionary<int, long> trackOffset = new Dictionary<int, long>(); + + public HdCopy() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy/Helpers.cs b/DiscImageChef.DiscImages/HDCopy/Helpers.cs new file mode 100644 index 000000000..d29fd36f3 --- /dev/null +++ b/DiscImageChef.DiscImages/HDCopy/Helpers.cs @@ -0,0 +1,83 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for HD-Copy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2017 Michael Drüing +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; + +namespace DiscImageChef.DiscImages +{ + public partial class HdCopy + { + void ReadTrackIntoCache(Stream stream, int tracknum) + { + byte[] trackData = new byte[imageInfo.SectorSize * imageInfo.SectorsPerTrack]; + byte[] blkHeader = new byte[3]; + + // check that track is present + if(trackOffset[tracknum] == -1) + throw new InvalidDataException("Tried reading a track that is not present in image"); + + stream.Seek(trackOffset[tracknum], SeekOrigin.Begin); + + // read the compressed track data + stream.Read(blkHeader, 0, 3); + short compressedLength = (short)(BitConverter.ToInt16(blkHeader, 0) - 1); + byte escapeByte = blkHeader[2]; + + byte[] cBuffer = new byte[compressedLength]; + stream.Read(cBuffer, 0, compressedLength); + + // decompress the data + int sIndex = 0; // source buffer position + int dIndex = 0; // destination buffer position + while(sIndex < compressedLength) + if(cBuffer[sIndex] == escapeByte) + { + sIndex++; // skip over escape byte + byte fillByte = cBuffer[sIndex++]; + byte fillCount = cBuffer[sIndex++]; + // fill destination buffer + for(int i = 0; i < fillCount; i++) trackData[dIndex++] = fillByte; + } + else + trackData[dIndex++] = cBuffer[sIndex++]; + + // check that the number of bytes decompressed matches a whole track + if(dIndex != imageInfo.SectorSize * imageInfo.SectorsPerTrack) + throw new InvalidDataException("Track decompression yielded incomplete data"); + + // store track in cache + trackCache[tracknum] = trackData; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy/Identify.cs b/DiscImageChef.DiscImages/HDCopy/Identify.cs new file mode 100644 index 000000000..e6faea188 --- /dev/null +++ b/DiscImageChef.DiscImages/HDCopy/Identify.cs @@ -0,0 +1,79 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies HD-Copy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2017 Michael Drüing +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class HdCopy + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 2 + 2 * 82) return false; + + byte[] header = new byte[2 + 2 * 82]; + stream.Read(header, 0, 2 + 2 * 82); + + IntPtr hdrPtr = Marshal.AllocHGlobal(2 + 2 * 82); + Marshal.Copy(header, 0, hdrPtr, 2 + 2 * 82); + HdcpFileHeader fheader = (HdcpFileHeader)Marshal.PtrToStructure(hdrPtr, typeof(HdcpFileHeader)); + Marshal.FreeHGlobal(hdrPtr); + + /* Some sanity checks on the values we just read. + * We know the image is from a DOS floppy disk, so assume + * some sane cylinder and sectors-per-track count. + */ + if(fheader.sectorsPerTrack < 8 || fheader.sectorsPerTrack > 40) return false; + + if(fheader.lastCylinder < 37 || fheader.lastCylinder >= 82) return false; + + // Validate the trackmap. First two tracks need to be present + if(fheader.trackMap[0] != 1 || fheader.trackMap[1] != 1) return false; + + // all other tracks must be either present (=1) or absent (=0) + for(int i = 0; i < 2 * 82; i++) + if(fheader.trackMap[i] > 1) + return false; + + // TODO: validate the tracks + // For now, having a valid header should be sufficient. + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy/Properties.cs b/DiscImageChef.DiscImages/HDCopy/Properties.cs new file mode 100644 index 000000000..ed4471163 --- /dev/null +++ b/DiscImageChef.DiscImages/HDCopy/Properties.cs @@ -0,0 +1,63 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for HD-Copy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2017 Michael Drüing +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class HdCopy + { + public ImageInfo Info => imageInfo; + + public string Name => "HD-Copy disk image"; + public Guid Id => new Guid("8D57483F-71A5-42EC-9B87-66AEC439C792"); + + public string Format => "HD-Copy image"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy/Read.cs b/DiscImageChef.DiscImages/HDCopy/Read.cs new file mode 100644 index 000000000..7bb4db997 --- /dev/null +++ b/DiscImageChef.DiscImages/HDCopy/Read.cs @@ -0,0 +1,152 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads HD-Copy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2017 Michael Drüing +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class HdCopy + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + byte[] header = new byte[2 + 2 * 82]; + stream.Read(header, 0, 2 + 2 * 82); + + IntPtr hdrPtr = Marshal.AllocHGlobal(2 + 2 * 82); + Marshal.Copy(header, 0, hdrPtr, 2 + 2 * 82); + HdcpFileHeader fheader = (HdcpFileHeader)Marshal.PtrToStructure(hdrPtr, typeof(HdcpFileHeader)); + Marshal.FreeHGlobal(hdrPtr); + DicConsole.DebugWriteLine("HDCP plugin", + "Detected HD-Copy image with {0} tracks and {1} sectors per track.", + fheader.lastCylinder + 1, fheader.sectorsPerTrack); + + imageInfo.Cylinders = (uint)fheader.lastCylinder + 1; + imageInfo.SectorsPerTrack = fheader.sectorsPerTrack; + imageInfo.SectorSize = 512; // only 512 bytes per sector supported + imageInfo.Heads = 2; // only 2-sided floppies are supported + imageInfo.Sectors = 2 * imageInfo.Cylinders * imageInfo.SectorsPerTrack; + imageInfo.ImageSize = imageInfo.Sectors * imageInfo.SectorSize; + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, 2, + (ushort)imageInfo.SectorsPerTrack, 512, MediaEncoding.MFM, + false)); + + // the start offset of the track data + long currentOffset = 2 + 2 * 82; + + // build table of track offsets + for(int i = 0; i < imageInfo.Cylinders * 2; i++) + if(fheader.trackMap[i] == 0) + trackOffset[i] = -1; + else + { + // track is present, read the block header + if(currentOffset + 3 >= stream.Length) return false; + + byte[] blkHeader = new byte[2]; + stream.Read(blkHeader, 0, 2); + short blkLength = BitConverter.ToInt16(blkHeader, 0); + + // assume block sizes are positive + if(blkLength < 0) return false; + + DicConsole.DebugWriteLine("HDCP plugin", "Track {0} offset 0x{1:x8}, size={2:x4}", i, currentOffset, + blkLength); + trackOffset[i] = currentOffset; + + currentOffset += 2 + blkLength; + // skip the block data + stream.Seek(blkLength, SeekOrigin.Current); + } + + // ensure that the last track is present completely + if(currentOffset > stream.Length) return false; + + // save some variables for later use + fileHeader = fheader; + hdcpImageFilter = imageFilter; + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + int trackNum = (int)(sectorAddress / imageInfo.SectorsPerTrack); + int sectorOffset = (int)(sectorAddress % imageInfo.SectorsPerTrack); + + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(trackNum > 2 * imageInfo.Cylinders) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + byte[] result = new byte[imageInfo.SectorSize]; + if(trackOffset[trackNum] == -1) Array.Clear(result, 0, (int)imageInfo.SectorSize); + else + { + // track is present in file, make sure it has been loaded + if(!trackCache.ContainsKey(trackNum)) ReadTrackIntoCache(hdcpImageFilter.GetDataForkStream(), trackNum); + + Array.Copy(trackCache[trackNum], sectorOffset * imageInfo.SectorSize, result, 0, imageInfo.SectorSize); + } + + return result; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + byte[] result = new byte[length * imageInfo.SectorSize]; + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + for(int i = 0; i < length; i++) + ReadSector(sectorAddress + (ulong)i).CopyTo(result, i * imageInfo.SectorSize); + + return result; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy/Structs.cs b/DiscImageChef.DiscImages/HDCopy/Structs.cs new file mode 100644 index 000000000..58452dcd5 --- /dev/null +++ b/DiscImageChef.DiscImages/HDCopy/Structs.cs @@ -0,0 +1,83 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for HD-Copy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2017 Michael Drüing +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class HdCopy + { + /// <summary> + /// The global header of a HDCP image file + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HdcpFileHeader + { + /// <summary> + /// Last cylinder (zero-based) + /// </summary> + public byte lastCylinder; + + /// <summary> + /// Sectors per track + /// </summary> + public byte sectorsPerTrack; + + /// <summary> + /// The track map. It contains one byte for each track. + /// Up to 82 tracks (41 tracks * 2 sides) are supported. + /// 0 means track is not present, 1 means it is present. + /// The first 2 tracks are always present. + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2 * 82)] + public byte[] trackMap; + } + + /// <summary> + /// The header for a RLE-compressed block + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HdcpBlockHeader + { + /// <summary> + /// The length of the compressed block, in bytes. Little-endian. + /// </summary> + public ushort length; + + /// <summary> + /// The byte value used as RLE escape sequence + /// </summary> + public byte escape; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/HDCopy/Unsupported.cs b/DiscImageChef.DiscImages/HDCopy/Unsupported.cs new file mode 100644 index 000000000..eb138a603 --- /dev/null +++ b/DiscImageChef.DiscImages/HDCopy/Unsupported.cs @@ -0,0 +1,139 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by HD-Copy disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2017 Michael Drüing +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class HdCopy + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/IMD/Constants.cs b/DiscImageChef.DiscImages/IMD/Constants.cs new file mode 100644 index 000000000..6dee10883 --- /dev/null +++ b/DiscImageChef.DiscImages/IMD/Constants.cs @@ -0,0 +1,43 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Dunfield's IMD disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Imd + { + const byte SECTOR_CYLINDER_MAP_MASK = 0x80; + const byte SECTOR_HEAD_MAP_MASK = 0x40; + const byte COMMENT_END = 0x1A; + const string REGEX_HEADER = + @"IMD (?<version>\d.\d+):\s+(?<day>\d+)\/\s*(?<month>\d+)\/(?<year>\d+)\s+(?<hour>\d+):(?<minute>\d+):(?<second>\d+)\r\n"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/IMD/Enums.cs b/DiscImageChef.DiscImages/IMD/Enums.cs new file mode 100644 index 000000000..bb712d4ed --- /dev/null +++ b/DiscImageChef.DiscImages/IMD/Enums.cs @@ -0,0 +1,66 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Dunfield's IMD disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Imd + { + enum TransferRate : byte + { + /// <summary>500 kbps in FM mode</summary> + FiveHundred = 0, + /// <summary>300 kbps in FM mode</summary> + ThreeHundred = 1, + /// <summary>250 kbps in FM mode</summary> + TwoHundred = 2, + /// <summary>500 kbps in MFM mode</summary> + FiveHundredMfm = 3, + /// <summary>300 kbps in MFM mode</summary> + ThreeHundredMfm = 4, + /// <summary>250 kbps in MFM mode</summary> + TwoHundredMfm = 5 + } + + enum SectorType : byte + { + Unavailable = 0, + Normal = 1, + Compressed = 2, + Deleted = 3, + CompressedDeleted = 4, + Error = 5, + CompressedError = 6, + DeletedError = 7, + CompressedDeletedError = 8 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/IMD/IMD.cs b/DiscImageChef.DiscImages/IMD/IMD.cs new file mode 100644 index 000000000..88df885b1 --- /dev/null +++ b/DiscImageChef.DiscImages/IMD/IMD.cs @@ -0,0 +1,72 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : IMD.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Dunfield's IMD disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Imd : IMediaImage + { + ImageInfo imageInfo; + List<byte[]> sectorsData; + + public Imd() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/IMD/Identify.cs b/DiscImageChef.DiscImages/IMD/Identify.cs new file mode 100644 index 000000000..3aaf957e3 --- /dev/null +++ b/DiscImageChef.DiscImages/IMD/Identify.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Dunfield's IMD disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Imd + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < 31) return false; + + byte[] hdr = new byte[31]; + stream.Read(hdr, 0, 31); + + Regex hr = new Regex(REGEX_HEADER); + Match hm = hr.Match(Encoding.ASCII.GetString(hdr)); + + return hm.Success; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/IMD/Properties.cs b/DiscImageChef.DiscImages/IMD/Properties.cs new file mode 100644 index 000000000..0bb120735 --- /dev/null +++ b/DiscImageChef.DiscImages/IMD/Properties.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Dunfield's IMD disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Imd + { + public string Name => "Dunfield's IMD"; + public Guid Id => new Guid("0D67162E-38A3-407D-9B1A-CF40080A48CB"); + public ImageInfo Info => imageInfo; + public string Format => "IMageDisk"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/IMD.cs b/DiscImageChef.DiscImages/IMD/Read.cs similarity index 53% rename from DiscImageChef.DiscImages/IMD.cs rename to DiscImageChef.DiscImages/IMD/Read.cs index a9278864d..f23f6d0d6 100644 --- a/DiscImageChef.DiscImages/IMD.cs +++ b/DiscImageChef.DiscImages/IMD/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : IMD.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Sydex IMD disc images. +// Reads Dunfield's IMD disk images. // // --[ License ] -------------------------------------------------------------- // @@ -33,86 +33,16 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; -using System.Text.RegularExpressions; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; namespace DiscImageChef.DiscImages { - public class Imd : IMediaImage + public partial class Imd { - const byte SECTOR_CYLINDER_MAP_MASK = 0x80; - const byte SECTOR_HEAD_MAP_MASK = 0x40; - const byte COMMENT_END = 0x1A; - const string REGEX_HEADER = - @"IMD (?<version>\d.\d+):\s+(?<day>\d+)\/\s*(?<month>\d+)\/(?<year>\d+)\s+(?<hour>\d+):(?<minute>\d+):(?<second>\d+)\r\n"; - ImageInfo imageInfo; - - List<byte[]> sectorsData; - - public Imd() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "Dunfield's IMD"; - public Guid Id => new Guid("0D67162E-38A3-407D-9B1A-CF40080A48CB"); - public ImageInfo Info => imageInfo; - - public string Format => "IMageDisk"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < 31) return false; - - byte[] hdr = new byte[31]; - stream.Read(hdr, 0, 31); - - Regex hr = new Regex(REGEX_HEADER); - Match hm = hr.Match(Encoding.ASCII.GetString(hdr)); - - return hm.Success; - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -230,8 +160,8 @@ namespace DiscImageChef.DiscImages mediaEncoding = MediaEncoding.FM; imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, - (ushort)imageInfo.SectorsPerTrack, imageInfo.SectorSize, - mediaEncoding, false)); + (ushort)imageInfo.SectorsPerTrack, imageInfo.SectorSize, + mediaEncoding, false)); switch(imageInfo.MediaType) { @@ -255,46 +185,6 @@ namespace DiscImageChef.DiscImages return true; } - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - public byte[] ReadSector(ulong sectorAddress) { return ReadSectors(sectorAddress, 1); @@ -314,99 +204,5 @@ namespace DiscImageChef.DiscImages return buffer.ToArray(); } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - enum TransferRate : byte - { - /// <summary>500 kbps in FM mode</summary> - FiveHundred = 0, - /// <summary>300 kbps in FM mode</summary> - ThreeHundred = 1, - /// <summary>250 kbps in FM mode</summary> - TwoHundred = 2, - /// <summary>500 kbps in MFM mode</summary> - FiveHundredMfm = 3, - /// <summary>300 kbps in MFM mode</summary> - ThreeHundredMfm = 4, - /// <summary>250 kbps in MFM mode</summary> - TwoHundredMfm = 5 - } - - enum SectorType : byte - { - Unavailable = 0, - Normal = 1, - Compressed = 2, - Deleted = 3, - CompressedDeleted = 4, - Error = 5, - CompressedError = 6, - DeletedError = 7, - CompressedDeletedError = 8 - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/IMD/Unsupported.cs b/DiscImageChef.DiscImages/IMD/Unsupported.cs new file mode 100644 index 000000000..68d3a9cc6 --- /dev/null +++ b/DiscImageChef.DiscImages/IMD/Unsupported.cs @@ -0,0 +1,144 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Dunfield's IMD disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Imd + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux/Constants.cs b/DiscImageChef.DiscImages/KryoFlux/Constants.cs new file mode 100644 index 000000000..bf661bd86 --- /dev/null +++ b/DiscImageChef.DiscImages/KryoFlux/Constants.cs @@ -0,0 +1,48 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for KryoFlux STREAM images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class KryoFlux + { + const string hostDate = "host_date"; + const string hostTime = "host_time"; + const string kfName = "name"; + const string kfVersion = "version"; + const string kfDate = "date"; + const string kfTime = "time"; + const string kfHwId = "hwid"; + const string kfHwRv = "hwrv"; + const string kfSck = "sck"; + const string kfIck = "ick"; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux/Enums.cs b/DiscImageChef.DiscImages/KryoFlux/Enums.cs new file mode 100644 index 000000000..18c405ee3 --- /dev/null +++ b/DiscImageChef.DiscImages/KryoFlux/Enums.cs @@ -0,0 +1,65 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for KryoFlux STREAM images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class KryoFlux + { + enum BlockIds : byte + { + Flux2 = 0x00, + Flux2_1 = 0x01, + Flux2_2 = 0x02, + Flux2_3 = 0x03, + Flux2_4 = 0x04, + Flux2_5 = 0x05, + Flux2_6 = 0x06, + Flux2_7 = 0x07, + Nop1 = 0x08, + Nop2 = 0x09, + Nop3 = 0x0A, + Ovl16 = 0x0B, + Flux3 = 0x0C, + Oob = 0x0D + } + + enum OobTypes : byte + { + Invalid = 0x00, + StreamInfo = 0x01, + Index = 0x02, + StreamEnd = 0x03, + KFInfo = 0x04, + EOF = 0x0D + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux/Identify.cs b/DiscImageChef.DiscImages/KryoFlux/Identify.cs new file mode 100644 index 000000000..1b50e1317 --- /dev/null +++ b/DiscImageChef.DiscImages/KryoFlux/Identify.cs @@ -0,0 +1,72 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies KryoFlux STREAM images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class KryoFlux + { + public bool Identify(IFilter imageFilter) + { + OobBlock header = new OobBlock(); + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < Marshal.SizeOf(header)) return false; + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + stream.Read(hdr, 0, Marshal.SizeOf(header)); + + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(header)); + header = (OobBlock)Marshal.PtrToStructure(hdrPtr, typeof(OobBlock)); + Marshal.FreeHGlobal(hdrPtr); + + OobBlock footer = new OobBlock(); + stream.Seek(-Marshal.SizeOf(footer), SeekOrigin.End); + + hdr = new byte[Marshal.SizeOf(footer)]; + stream.Read(hdr, 0, Marshal.SizeOf(footer)); + + hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(footer)); + Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(footer)); + footer = (OobBlock)Marshal.PtrToStructure(hdrPtr, typeof(OobBlock)); + Marshal.FreeHGlobal(hdrPtr); + + return header.blockId == BlockIds.Oob && header.blockType == OobTypes.KFInfo && + footer.blockId == BlockIds.Oob && footer.blockType == OobTypes.EOF && footer.length == 0x0D0D; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux/KryoFlux.cs b/DiscImageChef.DiscImages/KryoFlux/KryoFlux.cs new file mode 100644 index 000000000..237c1bc6a --- /dev/null +++ b/DiscImageChef.DiscImages/KryoFlux/KryoFlux.cs @@ -0,0 +1,74 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : KryoFlux.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages KryoFlux STREAM images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public partial class KryoFlux : IMediaImage + { + // TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0 + public SortedDictionary<byte, IFilter> tracks; + + public KryoFlux() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux/Properties.cs b/DiscImageChef.DiscImages/KryoFlux/Properties.cs new file mode 100644 index 000000000..9f5103ac8 --- /dev/null +++ b/DiscImageChef.DiscImages/KryoFlux/Properties.cs @@ -0,0 +1,59 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for KryoFlux STREAM images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class KryoFlux + { + public ImageInfo imageInfo; + public ImageInfo Info => imageInfo; + + public string Name => "KryoFlux STREAM"; + public Guid Id => new Guid("4DBC95E4-93EE-4F7A-9492-919887E60EFE"); + public string Format => "KryoFlux STREAM"; + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux.cs b/DiscImageChef.DiscImages/KryoFlux/Read.cs similarity index 63% rename from DiscImageChef.DiscImages/KryoFlux.cs rename to DiscImageChef.DiscImages/KryoFlux/Read.cs index 0001eb1d8..c1692b776 100644 --- a/DiscImageChef.DiscImages/KryoFlux.cs +++ b/DiscImageChef.DiscImages/KryoFlux/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : KryoFlux.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages KryoFlux STREAM images. +// Reads KryoFlux STREAM images. // // --[ License ] -------------------------------------------------------------- // @@ -32,101 +32,19 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Filters; -using Schemas; namespace DiscImageChef.DiscImages { - [SuppressMessage("ReSharper", "InconsistentNaming")] - public class KryoFlux : IMediaImage + public partial class KryoFlux { - const string hostDate = "host_date"; - const string hostTime = "host_time"; - const string kfName = "name"; - const string kfVersion = "version"; - const string kfDate = "date"; - const string kfTime = "time"; - const string kfHwId = "hwid"; - const string kfHwRv = "hwrv"; - const string kfSck = "sck"; - const string kfIck = "ick"; - - // TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0 - public SortedDictionary<byte, IFilter> tracks; - public ImageInfo imageInfo; - public ImageInfo Info => imageInfo; - - public string Name => "KryoFlux STREAM"; - public Guid Id => new Guid("4DBC95E4-93EE-4F7A-9492-919887E60EFE"); - - public KryoFlux() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public bool Identify(IFilter imageFilter) - { - OobBlock header = new OobBlock(); - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < Marshal.SizeOf(header)) return false; - - byte[] hdr = new byte[Marshal.SizeOf(header)]; - stream.Read(hdr, 0, Marshal.SizeOf(header)); - - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(header)); - header = (OobBlock)Marshal.PtrToStructure(hdrPtr, typeof(OobBlock)); - Marshal.FreeHGlobal(hdrPtr); - - OobBlock footer = new OobBlock(); - stream.Seek(-Marshal.SizeOf(footer), SeekOrigin.End); - - hdr = new byte[Marshal.SizeOf(footer)]; - stream.Read(hdr, 0, Marshal.SizeOf(footer)); - - hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(footer)); - Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(footer)); - footer = (OobBlock)Marshal.PtrToStructure(hdrPtr, typeof(OobBlock)); - Marshal.FreeHGlobal(hdrPtr); - - return header.blockId == BlockIds.Oob && header.blockType == OobTypes.KFInfo && - footer.blockId == BlockIds.Oob && footer.blockType == OobTypes.EOF && footer.length == 0x0D0D; - } - public bool Open(IFilter imageFilter) { OobBlock header = new OobBlock(); @@ -347,18 +265,11 @@ namespace DiscImageChef.DiscImages throw new NotImplementedException("Flux decoding is not yet implemented."); } - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) { throw new NotImplementedException("Flux decoding is not yet implemented."); } - public string Format => "KryoFlux STREAM"; - public bool? VerifySector(ulong sectorAddress) { throw new NotImplementedException("Flux decoding is not yet implemented."); @@ -369,104 +280,5 @@ namespace DiscImageChef.DiscImages { throw new NotImplementedException("Flux decoding is not yet implemented."); } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct OobBlock - { - public BlockIds blockId; - public OobTypes blockType; - public ushort length; - } - - enum BlockIds : byte - { - Flux2 = 0x00, - Flux2_1 = 0x01, - Flux2_2 = 0x02, - Flux2_3 = 0x03, - Flux2_4 = 0x04, - Flux2_5 = 0x05, - Flux2_6 = 0x06, - Flux2_7 = 0x07, - Nop1 = 0x08, - Nop2 = 0x09, - Nop3 = 0x0A, - Ovl16 = 0x0B, - Flux3 = 0x0C, - Oob = 0x0D - } - - enum OobTypes : byte - { - Invalid = 0x00, - StreamInfo = 0x01, - Index = 0x02, - StreamEnd = 0x03, - KFInfo = 0x04, - EOF = 0x0D - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux/Structs.cs b/DiscImageChef.DiscImages/KryoFlux/Structs.cs new file mode 100644 index 000000000..b55a2381d --- /dev/null +++ b/DiscImageChef.DiscImages/KryoFlux/Structs.cs @@ -0,0 +1,47 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for KryoFlux STREAM images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class KryoFlux + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct OobBlock + { + public BlockIds blockId; + public OobTypes blockType; + public ushort length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/KryoFlux/Unsupported.cs b/DiscImageChef.DiscImages/KryoFlux/Unsupported.cs new file mode 100644 index 000000000..61faf89bb --- /dev/null +++ b/DiscImageChef.DiscImages/KryoFlux/Unsupported.cs @@ -0,0 +1,98 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by KryoFlux STREAM images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class KryoFlux + { + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk.cs b/DiscImageChef.DiscImages/MaxiDisk.cs deleted file mode 100644 index 739bcf606..000000000 --- a/DiscImageChef.DiscImages/MaxiDisk.cs +++ /dev/null @@ -1,605 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : MaxiDisk.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages MaxiDisk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using DiscImageChef.Helpers; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class MaxiDisk : IWritableImage - { - /// <summary>Disk image file</summary> - IFilter hdkImageFilter; - ImageInfo imageInfo; - FileStream writingStream; - - public MaxiDisk() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Application = "MAXI Disk", - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "MAXI Disk image"; - public Guid Id => new Guid("D27D924A-7034-466E-ADE1-B81EF37E469E"); - - public string Format => "MAXI Disk"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if(stream.Length < 8) return false; - - byte[] buffer = new byte[8]; - stream.Seek(0, SeekOrigin.Begin); - stream.Read(buffer, 0, buffer.Length); - - IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); - Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); - HdkHeader tmpHeader = (HdkHeader)Marshal.PtrToStructure(ftrPtr, typeof(HdkHeader)); - Marshal.FreeHGlobal(ftrPtr); - - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.unknown = {0}", tmpHeader.unknown); - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.diskType = {0}", tmpHeader.diskType); - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.heads = {0}", tmpHeader.heads); - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.cylinders = {0}", tmpHeader.cylinders); - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.bytesPerSector = {0}", tmpHeader.bytesPerSector); - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.sectorsPerTrack = {0}", - tmpHeader.sectorsPerTrack); - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.unknown2 = {0}", tmpHeader.unknown2); - DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.unknown3 = {0}", tmpHeader.unknown3); - - // This is hardcoded - // But its possible values are unknown... - //if(tmp_header.diskType > 11) - // return false; - - // Only floppies supported - if(tmpHeader.heads == 0 || tmpHeader.heads > 2) return false; - - // No floppies with more than this? - if(tmpHeader.cylinders > 90) return false; - - // Maximum supported bps is 16384 - if(tmpHeader.bytesPerSector > 7) return false; - - int expectedFileSize = tmpHeader.heads * tmpHeader.cylinders * tmpHeader.sectorsPerTrack * - (128 << tmpHeader.bytesPerSector) + 8; - - return expectedFileSize == stream.Length; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if(stream.Length < 8) return false; - - byte[] buffer = new byte[8]; - stream.Seek(0, SeekOrigin.Begin); - stream.Read(buffer, 0, buffer.Length); - - IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); - Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); - HdkHeader tmpHeader = (HdkHeader)Marshal.PtrToStructure(ftrPtr, typeof(HdkHeader)); - Marshal.FreeHGlobal(ftrPtr); - - // This is hardcoded - // But its possible values are unknown... - //if(tmp_header.diskType > 11) - // return false; - - // Only floppies supported - if(tmpHeader.heads == 0 || tmpHeader.heads > 2) return false; - - // No floppies with more than this? - if(tmpHeader.cylinders > 90) return false; - - // Maximum supported bps is 16384 - if(tmpHeader.bytesPerSector > 7) return false; - - int expectedFileSize = tmpHeader.heads * tmpHeader.cylinders * tmpHeader.sectorsPerTrack * - (128 << tmpHeader.bytesPerSector) + 8; - - if(expectedFileSize != stream.Length) return false; - - imageInfo.Cylinders = tmpHeader.cylinders; - imageInfo.Heads = tmpHeader.heads; - imageInfo.SectorsPerTrack = tmpHeader.sectorsPerTrack; - imageInfo.Sectors = (ulong)(tmpHeader.heads * tmpHeader.cylinders * tmpHeader.sectorsPerTrack); - imageInfo.SectorSize = (uint)(128 << tmpHeader.bytesPerSector); - - hdkImageFilter = imageFilter; - - imageInfo.ImageSize = (ulong)(stream.Length - 8); - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - - imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, - (ushort)imageInfo.SectorsPerTrack, imageInfo.SectorSize, - MediaEncoding.MFM, false)); - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - - return true; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Stream stream = hdkImageFilter.GetDataForkStream(); - stream.Seek((long)(8 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - - return buffer; - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - // TODO: Test with real hardware to see real supported media - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, - MediaType.ATARI_35_SS_DD_11, MediaType.DMF, MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, - MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, - MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, - MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, MediaType.FDFORMAT_35_DD, - MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, MediaType.FDFORMAT_525_HD, MediaType.RX50, - MediaType.XDF_35, MediaType.XDF_525 - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".hdk"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(CountBits.Count(sectorSize) != 1 || sectorSize > 16384) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors > 90 * 2 * 255) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool - variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); - imageInfo = new ImageInfo - { - MediaType = mediaType, - SectorSize = sectorSize, - Sectors = sectors, - Cylinders = geometry.cylinders, - Heads = geometry.heads, - SectorsPerTrack = geometry.sectorsPerTrack - }; - - if(imageInfo.Cylinders > 90) - { - ErrorMessage = "Too many cylinders"; - return false; - } - - if(imageInfo.Heads > 2) - { - ErrorMessage = "Too many heads"; - return false; - } - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(HdkHeader)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(HdkHeader)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - HdkHeader header = new HdkHeader - { - diskType = (byte)HdkDiskTypes.Dos2880, - cylinders = (byte)imageInfo.Cylinders, - heads = (byte)imageInfo.Heads, - sectorsPerTrack = (byte)imageInfo.SectorsPerTrack - }; - - for(uint i = imageInfo.SectorSize / 128; i > 1;) - { - header.bytesPerSector++; - i >>= 1; - } - - byte[] hdr = new byte[Marshal.SizeOf(header)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.StructureToPtr(header, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > 90) - { - ErrorMessage = "Too many cylinders."; - return false; - } - - if(heads > 2) - { - ErrorMessage = "Too many heads."; - return false; - } - - if(sectorsPerTrack > byte.MaxValue) - { - ErrorMessage = "Too many sectors per track."; - return false; - } - - imageInfo.SectorsPerTrack = sectorsPerTrack; - imageInfo.Heads = heads; - imageInfo.Cylinders = cylinders; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct HdkHeader - { - public byte unknown; - public byte diskType; - public byte heads; - public byte cylinders; - public byte bytesPerSector; - public byte sectorsPerTrack; - public byte unknown2; - public byte unknown3; - } - - enum HdkDiskTypes : byte - { - Dos360 = 0, - Maxi420 = 1, - Dos720 = 2, - Maxi800 = 3, - Dos1200 = 4, - Maxi1400 = 5, - Dos1440 = 6, - Mac1440 = 7, - Maxi1600 = 8, - Dmf = 9, - Dos2880 = 10, - Maxi3200 = 11 - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/Enums.cs b/DiscImageChef.DiscImages/MaxiDisk/Enums.cs new file mode 100644 index 000000000..96dd69fba --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/Enums.cs @@ -0,0 +1,53 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for MaxiDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk + { + enum HdkDiskTypes : byte + { + Dos360 = 0, + Maxi420 = 1, + Dos720 = 2, + Maxi800 = 3, + Dos1200 = 4, + Maxi1400 = 5, + Dos1440 = 6, + Mac1440 = 7, + Maxi1600 = 8, + Dmf = 9, + Dos2880 = 10, + Maxi3200 = 11 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/Identify.cs b/DiscImageChef.DiscImages/MaxiDisk/Identify.cs new file mode 100644 index 000000000..f4591d84d --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/Identify.cs @@ -0,0 +1,88 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies MaxiDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if(stream.Length < 8) return false; + + byte[] buffer = new byte[8]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(buffer, 0, buffer.Length); + + IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); + HdkHeader tmpHeader = (HdkHeader)Marshal.PtrToStructure(ftrPtr, typeof(HdkHeader)); + Marshal.FreeHGlobal(ftrPtr); + + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.unknown = {0}", tmpHeader.unknown); + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.diskType = {0}", tmpHeader.diskType); + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.heads = {0}", tmpHeader.heads); + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.cylinders = {0}", tmpHeader.cylinders); + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.bytesPerSector = {0}", tmpHeader.bytesPerSector); + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.sectorsPerTrack = {0}", + tmpHeader.sectorsPerTrack); + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.unknown2 = {0}", tmpHeader.unknown2); + DicConsole.DebugWriteLine("MAXI Disk plugin", "tmp_header.unknown3 = {0}", tmpHeader.unknown3); + + // This is hardcoded + // But its possible values are unknown... + //if(tmp_header.diskType > 11) + // return false; + + // Only floppies supported + if(tmpHeader.heads == 0 || tmpHeader.heads > 2) return false; + + // No floppies with more than this? + if(tmpHeader.cylinders > 90) return false; + + // Maximum supported bps is 16384 + if(tmpHeader.bytesPerSector > 7) return false; + + int expectedFileSize = tmpHeader.heads * tmpHeader.cylinders * tmpHeader.sectorsPerTrack * + (128 << tmpHeader.bytesPerSector) + 8; + + return expectedFileSize == stream.Length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/MaxiDisk.cs b/DiscImageChef.DiscImages/MaxiDisk/MaxiDisk.cs new file mode 100644 index 000000000..972fa0395 --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/MaxiDisk.cs @@ -0,0 +1,73 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : MaxiDisk.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages MaxiDisk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk : IWritableImage + { + /// <summary>Disk image file</summary> + IFilter hdkImageFilter; + ImageInfo imageInfo; + FileStream writingStream; + + public MaxiDisk() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Application = "MAXI Disk", + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/Properties.cs b/DiscImageChef.DiscImages/MaxiDisk/Properties.cs new file mode 100644 index 000000000..6678bfc90 --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/Properties.cs @@ -0,0 +1,83 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for MaxiDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk + { + public ImageInfo Info => imageInfo; + + public string Name => "MAXI Disk image"; + public Guid Id => new Guid("D27D924A-7034-466E-ADE1-B81EF37E469E"); + + public string Format => "MAXI Disk"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + // TODO: Test with real hardware to see real supported media + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, + MediaType.ATARI_35_SS_DD_11, MediaType.DMF, MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, + MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, + MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, + MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, MediaType.FDFORMAT_35_DD, + MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, MediaType.FDFORMAT_525_HD, MediaType.RX50, + MediaType.XDF_35, MediaType.XDF_525 + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".hdk"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/Read.cs b/DiscImageChef.DiscImages/MaxiDisk/Read.cs new file mode 100644 index 000000000..181e6150a --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/Read.cs @@ -0,0 +1,121 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads MaxiDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if(stream.Length < 8) return false; + + byte[] buffer = new byte[8]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(buffer, 0, buffer.Length); + + IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); + HdkHeader tmpHeader = (HdkHeader)Marshal.PtrToStructure(ftrPtr, typeof(HdkHeader)); + Marshal.FreeHGlobal(ftrPtr); + + // This is hardcoded + // But its possible values are unknown... + //if(tmp_header.diskType > 11) + // return false; + + // Only floppies supported + if(tmpHeader.heads == 0 || tmpHeader.heads > 2) return false; + + // No floppies with more than this? + if(tmpHeader.cylinders > 90) return false; + + // Maximum supported bps is 16384 + if(tmpHeader.bytesPerSector > 7) return false; + + int expectedFileSize = tmpHeader.heads * tmpHeader.cylinders * tmpHeader.sectorsPerTrack * + (128 << tmpHeader.bytesPerSector) + 8; + + if(expectedFileSize != stream.Length) return false; + + imageInfo.Cylinders = tmpHeader.cylinders; + imageInfo.Heads = tmpHeader.heads; + imageInfo.SectorsPerTrack = tmpHeader.sectorsPerTrack; + imageInfo.Sectors = (ulong)(tmpHeader.heads * tmpHeader.cylinders * tmpHeader.sectorsPerTrack); + imageInfo.SectorSize = (uint)(128 << tmpHeader.bytesPerSector); + + hdkImageFilter = imageFilter; + + imageInfo.ImageSize = (ulong)(stream.Length - 8); + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + + imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, + (ushort)imageInfo.SectorsPerTrack, imageInfo.SectorSize, + MediaEncoding.MFM, false)); + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Stream stream = hdkImageFilter.GetDataForkStream(); + stream.Seek((long)(8 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/Structs.cs b/DiscImageChef.DiscImages/MaxiDisk/Structs.cs new file mode 100644 index 000000000..695809848 --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/Structs.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for MaxiDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HdkHeader + { + public byte unknown; + public byte diskType; + public byte heads; + public byte cylinders; + public byte bytesPerSector; + public byte sectorsPerTrack; + public byte unknown2; + public byte unknown3; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/Unsupported.cs b/DiscImageChef.DiscImages/MaxiDisk/Unsupported.cs new file mode 100644 index 000000000..d23f61b91 --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/Unsupported.cs @@ -0,0 +1,144 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by MaxiDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/MaxiDisk/Write.cs b/DiscImageChef.DiscImages/MaxiDisk/Write.cs new file mode 100644 index 000000000..2ebe87c05 --- /dev/null +++ b/DiscImageChef.DiscImages/MaxiDisk/Write.cs @@ -0,0 +1,280 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes MaxiDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using DiscImageChef.Helpers; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class MaxiDisk + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(CountBits.Count(sectorSize) != 1 || sectorSize > 16384) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors > 90 * 2 * 255) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool + variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); + imageInfo = new ImageInfo + { + MediaType = mediaType, + SectorSize = sectorSize, + Sectors = sectors, + Cylinders = geometry.cylinders, + Heads = geometry.heads, + SectorsPerTrack = geometry.sectorsPerTrack + }; + + if(imageInfo.Cylinders > 90) + { + ErrorMessage = "Too many cylinders"; + return false; + } + + if(imageInfo.Heads > 2) + { + ErrorMessage = "Too many heads"; + return false; + } + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(HdkHeader)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(HdkHeader)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + HdkHeader header = new HdkHeader + { + diskType = (byte)HdkDiskTypes.Dos2880, + cylinders = (byte)imageInfo.Cylinders, + heads = (byte)imageInfo.Heads, + sectorsPerTrack = (byte)imageInfo.SectorsPerTrack + }; + + for(uint i = imageInfo.SectorSize / 128; i > 1;) + { + header.bytesPerSector++; + i >>= 1; + } + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.StructureToPtr(header, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > 90) + { + ErrorMessage = "Too many cylinders."; + return false; + } + + if(heads > 2) + { + ErrorMessage = "Too many heads."; + return false; + } + + if(sectorsPerTrack > byte.MaxValue) + { + ErrorMessage = "Too many sectors per track."; + return false; + } + + imageInfo.SectorsPerTrack = sectorsPerTrack; + imageInfo.Heads = heads; + imageInfo.Cylinders = cylinders; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NDIF/Constants.cs b/DiscImageChef.DiscImages/NDIF/Constants.cs new file mode 100644 index 000000000..54fbc1b2d --- /dev/null +++ b/DiscImageChef.DiscImages/NDIF/Constants.cs @@ -0,0 +1,67 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Apple New Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Ndif + { + /// <summary> + /// Resource OSType for NDIF is "bcem" + /// </summary> + const uint NDIF_RESOURCE = 0x6263656D; + /// <summary> + /// Resource ID is always 128? Never found another + /// </summary> + const short NDIF_RESOURCEID = 128; + + const byte CHUNK_TYPE_NOCOPY = 0; + const byte CHUNK_TYPE_COPY = 2; + const byte CHUNK_TYPE_KENCODE = 0x80; + const byte CHUNK_TYPE_RLE = 0x81; + const byte CHUNK_TYPE_LZH = 0x82; + const byte CHUNK_TYPE_ADC = 0x83; + /// <summary> + /// Created by ShrinkWrap 3.5, dunno which version of the StuffIt algorithm it is using + /// </summary> + const byte CHUNK_TYPE_STUFFIT = 0xF0; + const byte CHUNK_TYPE_END = 0xFF; + const byte CHUNK_TYPE_COMPRESSED_MASK = 0x80; + + const short DRIVER_OSX = -1; + const short DRIVER_HFS = 0; + const short DRIVER_PRODOS = 256; + const short DRIVER_DOS = 18771; + const uint MAX_CACHE_SIZE = 16777216; + const uint SECTOR_SIZE = 512; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NDIF/Identify.cs b/DiscImageChef.DiscImages/NDIF/Identify.cs new file mode 100644 index 000000000..fe7bae280 --- /dev/null +++ b/DiscImageChef.DiscImages/NDIF/Identify.cs @@ -0,0 +1,59 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Apple New Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using Claunia.RsrcFork; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Ndif + { + public bool Identify(IFilter imageFilter) + { + if(!imageFilter.HasResourceFork() || imageFilter.GetResourceForkLength() == 0) return false; + + try + { + ResourceFork rsrcFork = new ResourceFork(imageFilter.GetResourceForkStream()); + if(!rsrcFork.ContainsKey(NDIF_RESOURCE)) return false; + + Resource rsrc = rsrcFork.GetResource(NDIF_RESOURCE); + + if(rsrc.ContainsId(NDIF_RESOURCEID)) return true; + } + catch(InvalidCastException) { return false; } + + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NDIF/NDIF.cs b/DiscImageChef.DiscImages/NDIF/NDIF.cs new file mode 100644 index 000000000..76758ea4d --- /dev/null +++ b/DiscImageChef.DiscImages/NDIF/NDIF.cs @@ -0,0 +1,83 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : NDIF.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Apple New Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Detect OS X encrypted images + // TODO: Check checksum + // TODO: Implement segments + // TODO: Implement compression + public partial class Ndif : IMediaImage + { + uint buffersize; + Dictionary<ulong, byte[]> chunkCache; + Dictionary<ulong, BlockChunk> chunks; + uint currentChunkCacheSize; + ChunkHeader header; + ImageInfo imageInfo; + Stream imageStream; + Dictionary<ulong, byte[]> sectorCache; + + public Ndif() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NDIF/Properties.cs b/DiscImageChef.DiscImages/NDIF/Properties.cs new file mode 100644 index 000000000..166451d3d --- /dev/null +++ b/DiscImageChef.DiscImages/NDIF/Properties.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Apple New Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Ndif + { + public ImageInfo Info => imageInfo; + public string Name => "Apple New Disk Image Format"; + public Guid Id => new Guid("5A7FF7D8-491E-458D-8674-5B5EADBECC24"); + public string Format => "Apple New Disk Image Format"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NDIF.cs b/DiscImageChef.DiscImages/NDIF/Read.cs similarity index 61% rename from DiscImageChef.DiscImages/NDIF.cs rename to DiscImageChef.DiscImages/NDIF/Read.cs index ac0e00a0f..d707c2b0a 100644 --- a/DiscImageChef.DiscImages/NDIF.cs +++ b/DiscImageChef.DiscImages/NDIF/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : NDIF.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Apple New Disk Image Format. +// Reads Apple New Disk Image Format. // // --[ License ] -------------------------------------------------------------- // @@ -34,131 +34,21 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; -using System.Text; +using Claunia.Encoding; using Claunia.RsrcFork; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Compression; using DiscImageChef.Console; -using Schemas; using SharpCompress.Compressors.ADC; using Version = Resources.Version; namespace DiscImageChef.DiscImages { - // TODO: Detect OS X encrypted images - // TODO: Check checksum - // TODO: Implement segments - // TODO: Implement compression - public class Ndif : IMediaImage + public partial class Ndif { - /// <summary> - /// Resource OSType for NDIF is "bcem" - /// </summary> - const uint NDIF_RESOURCE = 0x6263656D; - /// <summary> - /// Resource ID is always 128? Never found another - /// </summary> - const short NDIF_RESOURCEID = 128; - - const byte CHUNK_TYPE_NOCOPY = 0; - const byte CHUNK_TYPE_COPY = 2; - const byte CHUNK_TYPE_KENCODE = 0x80; - const byte CHUNK_TYPE_RLE = 0x81; - const byte CHUNK_TYPE_LZH = 0x82; - const byte CHUNK_TYPE_ADC = 0x83; - /// <summary> - /// Created by ShrinkWrap 3.5, dunno which version of the StuffIt algorithm it is using - /// </summary> - const byte CHUNK_TYPE_STUFFIT = 0xF0; - const byte CHUNK_TYPE_END = 0xFF; - - const byte CHUNK_TYPE_COMPRESSED_MASK = 0x80; - - const short DRIVER_OSX = -1; - const short DRIVER_HFS = 0; - const short DRIVER_PRODOS = 256; - const short DRIVER_DOS = 18771; - const uint MAX_CACHE_SIZE = 16777216; - const uint SECTOR_SIZE = 512; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; - uint buffersize; - Dictionary<ulong, byte[]> chunkCache; - Dictionary<ulong, BlockChunk> chunks; - uint currentChunkCacheSize; - - ChunkHeader header; - ImageInfo imageInfo; - - Stream imageStream; - - Dictionary<ulong, byte[]> sectorCache; - - public Ndif() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Apple New Disk Image Format"; - public Guid Id => new Guid("5A7FF7D8-491E-458D-8674-5B5EADBECC24"); - - public string Format => "Apple New Disk Image Format"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - if(!imageFilter.HasResourceFork() || imageFilter.GetResourceForkLength() == 0) return false; - - try - { - ResourceFork rsrcFork = new ResourceFork(imageFilter.GetResourceForkStream()); - if(!rsrcFork.ContainsKey(NDIF_RESOURCE)) return false; - - Resource rsrc = rsrcFork.GetResource(NDIF_RESOURCE); - - if(rsrc.ContainsId(NDIF_RESOURCEID)) return true; - } - catch(InvalidCastException) { return false; } - - return false; - } - public bool Open(IFilter imageFilter) { if(!imageFilter.HasResourceFork() || imageFilter.GetResourceForkLength() == 0) return false; @@ -512,209 +402,5 @@ namespace DiscImageChef.DiscImages return ms.ToArray(); } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ChunkHeader - { - /// <summary> - /// Version - /// </summary> - public short version; - /// <summary> - /// Filesystem ID - /// </summary> - public short driver; - /// <summary> - /// Disk image name, Str63 (Pascal string) - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] - public byte[] name; - /// <summary> - /// Sectors in image - /// </summary> - public uint sectors; - /// <summary> - /// Maximum number of sectors per chunk - /// </summary> - public uint maxSectorsPerChunk; - /// <summary> - /// Offset to add to every chunk offset - /// </summary> - public uint dataOffset; - /// <summary> - /// CRC28 of whole image - /// </summary> - public uint crc; - /// <summary> - /// Set to 1 if segmented - /// </summary> - public uint segmented; - /// <summary> - /// Unknown - /// </summary> - public uint p1; - /// <summary> - /// Unknown - /// </summary> - public uint p2; - /// <summary> - /// Unknown, spare? - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] - public uint[] unknown; - /// <summary> - /// Set to 1 by ShrinkWrap if image is encrypted - /// </summary> - public uint encrypted; - /// <summary> - /// Set by ShrinkWrap if image is encrypted, value is the same for same password - /// </summary> - public uint hash; - /// <summary> - /// How many chunks follow the header - /// </summary> - public uint chunks; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct BlockChunk - { - /// <summary> - /// Starting sector, 3 bytes - /// </summary> - public uint sector; - /// <summary> - /// Chunk type - /// </summary> - public byte type; - /// <summary> - /// Offset in start of chunk - /// </summary> - public uint offset; - /// <summary> - /// Length in bytes of chunk - /// </summary> - public uint length; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct SegmentHeader - { - /// <summary> - /// Segment # - /// </summary> - public ushort segment; - /// <summary> - /// How many segments - /// </summary> - public ushort segments; - /// <summary> - /// Seems to be a Guid, changes with different images, same for all segments of same image - /// </summary> - public Guid segmentId; - /// <summary> - /// Seems to be a CRC28 of this segment, unchecked - /// </summary> - public uint crc; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NDIF/Structs.cs b/DiscImageChef.DiscImages/NDIF/Structs.cs new file mode 100644 index 000000000..770193372 --- /dev/null +++ b/DiscImageChef.DiscImages/NDIF/Structs.cs @@ -0,0 +1,145 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Apple New Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Ndif + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChunkHeader + { + /// <summary> + /// Version + /// </summary> + public short version; + /// <summary> + /// Filesystem ID + /// </summary> + public short driver; + /// <summary> + /// Disk image name, Str63 (Pascal string) + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public byte[] name; + /// <summary> + /// Sectors in image + /// </summary> + public uint sectors; + /// <summary> + /// Maximum number of sectors per chunk + /// </summary> + public uint maxSectorsPerChunk; + /// <summary> + /// Offset to add to every chunk offset + /// </summary> + public uint dataOffset; + /// <summary> + /// CRC28 of whole image + /// </summary> + public uint crc; + /// <summary> + /// Set to 1 if segmented + /// </summary> + public uint segmented; + /// <summary> + /// Unknown + /// </summary> + public uint p1; + /// <summary> + /// Unknown + /// </summary> + public uint p2; + /// <summary> + /// Unknown, spare? + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public uint[] unknown; + /// <summary> + /// Set to 1 by ShrinkWrap if image is encrypted + /// </summary> + public uint encrypted; + /// <summary> + /// Set by ShrinkWrap if image is encrypted, value is the same for same password + /// </summary> + public uint hash; + /// <summary> + /// How many chunks follow the header + /// </summary> + public uint chunks; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct BlockChunk + { + /// <summary> + /// Starting sector, 3 bytes + /// </summary> + public uint sector; + /// <summary> + /// Chunk type + /// </summary> + public byte type; + /// <summary> + /// Offset in start of chunk + /// </summary> + public uint offset; + /// <summary> + /// Length in bytes of chunk + /// </summary> + public uint length; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SegmentHeader + { + /// <summary> + /// Segment # + /// </summary> + public ushort segment; + /// <summary> + /// How many segments + /// </summary> + public ushort segments; + /// <summary> + /// Seems to be a Guid, changes with different images, same for all segments of same image + /// </summary> + public Guid segmentId; + /// <summary> + /// Seems to be a CRC28 of this segment, unchecked + /// </summary> + public uint crc; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NDIF/Unsupported.cs b/DiscImageChef.DiscImages/NDIF/Unsupported.cs new file mode 100644 index 000000000..72efb7c2a --- /dev/null +++ b/DiscImageChef.DiscImages/NDIF/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Apple New Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Ndif + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0.cs b/DiscImageChef.DiscImages/NHDr0.cs deleted file mode 100644 index 5cfc75528..000000000 --- a/DiscImageChef.DiscImages/NHDr0.cs +++ /dev/null @@ -1,559 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : NHDr0.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages NHD r0 disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - // Info from http://www.geocities.jp/t98next/nhdr0.txt - public class Nhdr0 : IWritableImage - { - readonly byte[] signature = - {0x54, 0x39, 0x38, 0x48, 0x44, 0x44, 0x49, 0x4D, 0x41, 0x47, 0x45, 0x2E, 0x52, 0x30, 0x00}; - ImageInfo imageInfo; - - Nhdr0Header nhdhdr; - IFilter nhdImageFilter; - FileStream writingStream; - - public Nhdr0() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "T98-Next NHD r0 Disk Image"; - public Guid Id => new Guid("6ECACD0A-8F4D-4465-8815-AEA000D370E3"); - public ImageInfo Info => imageInfo; - - public string Format => "NHDr0 disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p - Encoding shiftjis = Encoding.GetEncoding("shift_jis"); - - nhdhdr = new Nhdr0Header(); - - if(stream.Length < Marshal.SizeOf(nhdhdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(nhdhdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - nhdhdr = (Nhdr0Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Nhdr0Header)); - handle.Free(); - - if(!nhdhdr.szFileID.SequenceEqual(signature)) return false; - - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.szFileID = \"{0}\"", - StringHandlers.CToString(nhdhdr.szFileID, shiftjis)); - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.reserved1 = {0}", nhdhdr.reserved1); - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.szComment = \"{0}\"", - StringHandlers.CToString(nhdhdr.szComment, shiftjis)); - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.dwHeadSize = {0}", nhdhdr.dwHeadSize); - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.dwCylinder = {0}", nhdhdr.dwCylinder); - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.wHead = {0}", nhdhdr.wHead); - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.wSect = {0}", nhdhdr.wSect); - DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.wSectLen = {0}", nhdhdr.wSectLen); - - return true; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p - Encoding shiftjis = Encoding.GetEncoding("shift_jis"); - - nhdhdr = new Nhdr0Header(); - - if(stream.Length < Marshal.SizeOf(nhdhdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(nhdhdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - nhdhdr = (Nhdr0Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Nhdr0Header)); - handle.Free(); - - imageInfo.MediaType = MediaType.GENERIC_HDD; - - imageInfo.ImageSize = (ulong)(stream.Length - nhdhdr.dwHeadSize); - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = (ulong)(nhdhdr.dwCylinder * nhdhdr.wHead * nhdhdr.wSect); - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.SectorSize = (uint)nhdhdr.wSectLen; - imageInfo.Cylinders = (uint)nhdhdr.dwCylinder; - imageInfo.Heads = (uint)nhdhdr.wHead; - imageInfo.SectorsPerTrack = (uint)nhdhdr.wSect; - imageInfo.Comments = StringHandlers.CToString(nhdhdr.szComment, shiftjis); - - nhdImageFilter = imageFilter; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Stream stream = nhdImageFilter.GetDataForkStream(); - - stream.Seek((long)((ulong)nhdhdr.dwHeadSize + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - - return buffer; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => new[] {MediaType.GENERIC_HDD, MediaType.Unknown}; - // TODO: Support dynamic images - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".nhd"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 0) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream - .Seek((long)((ulong)Marshal.SizeOf(typeof(Nhdr0Header)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream - .Seek((long)((ulong)Marshal.SizeOf(typeof(Nhdr0Header)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(imageInfo.Cylinders == 0) - { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 8 / 17); - imageInfo.Heads = 8; - imageInfo.SectorsPerTrack = 17; - - while(imageInfo.Cylinders == 0) - { - imageInfo.Heads--; - - if(imageInfo.Heads == 0) - { - imageInfo.SectorsPerTrack--; - imageInfo.Heads = 16; - } - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); - - if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; - } - } - - Nhdr0Header header = new Nhdr0Header - { - szFileID = signature, - szComment = new byte[0x100], - dwHeadSize = Marshal.SizeOf(typeof(Nhdr0Header)), - dwCylinder = (byte)imageInfo.Cylinders, - wHead = (byte)imageInfo.Heads, - wSect = (byte)imageInfo.SectorsPerTrack, - wSectLen = (byte)imageInfo.SectorSize, - reserved2 = new byte[2], - reserved3 = new byte[0xE0] - }; - - if(!string.IsNullOrEmpty(imageInfo.Comments)) - { - byte[] commentBytes = Encoding.GetEncoding("shift_jis").GetBytes(imageInfo.Comments); - Array.Copy(commentBytes, 0, header.szComment, 0, - commentBytes.Length >= 0x100 ? 0x100 : commentBytes.Length); - } - - byte[] hdr = new byte[Marshal.SizeOf(header)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.StructureToPtr(header, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - IsWriting = false; - - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.Comments = metadata.Comments; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > int.MaxValue) - { - ErrorMessage = "Too many cylinders."; - return false; - } - - if(heads > short.MaxValue) - { - ErrorMessage = "Too many heads."; - return false; - } - - if(sectorsPerTrack > short.MaxValue) - { - ErrorMessage = "Too many sectors per track."; - return false; - } - - imageInfo.SectorsPerTrack = sectorsPerTrack; - imageInfo.Heads = heads; - imageInfo.Cylinders = cylinders; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct Nhdr0Header - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] - public byte[] szFileID; - public byte reserved1; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] - public byte[] szComment; - public int dwHeadSize; - public int dwCylinder; - public short wHead; - public short wSect; - public short wSectLen; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public byte[] reserved2; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xE0)] - public byte[] reserved3; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/Constants.cs b/DiscImageChef.DiscImages/NHDr0/Constants.cs new file mode 100644 index 000000000..ad07a53c4 --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/Constants.cs @@ -0,0 +1,40 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Nhdr0 + { + readonly byte[] signature = + {0x54, 0x39, 0x38, 0x48, 0x44, 0x44, 0x49, 0x4D, 0x41, 0x47, 0x45, 0x2E, 0x52, 0x30, 0x00}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/Identify.cs b/DiscImageChef.DiscImages/NHDr0/Identify.cs new file mode 100644 index 000000000..fe0beebd3 --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/Identify.cs @@ -0,0 +1,78 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Nhdr0 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p + Encoding shiftjis = Encoding.GetEncoding("shift_jis"); + + nhdhdr = new Nhdr0Header(); + + if(stream.Length < Marshal.SizeOf(nhdhdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(nhdhdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + nhdhdr = (Nhdr0Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Nhdr0Header)); + handle.Free(); + + if(!nhdhdr.szFileID.SequenceEqual(signature)) return false; + + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.szFileID = \"{0}\"", + StringHandlers.CToString(nhdhdr.szFileID, shiftjis)); + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.reserved1 = {0}", nhdhdr.reserved1); + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.szComment = \"{0}\"", + StringHandlers.CToString(nhdhdr.szComment, shiftjis)); + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.dwHeadSize = {0}", nhdhdr.dwHeadSize); + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.dwCylinder = {0}", nhdhdr.dwCylinder); + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.wHead = {0}", nhdhdr.wHead); + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.wSect = {0}", nhdhdr.wSect); + DicConsole.DebugWriteLine("NHDr0 plugin", "nhdhdr.wSectLen = {0}", nhdhdr.wSectLen); + + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/NHDr0.cs b/DiscImageChef.DiscImages/NHDr0/NHDr0.cs new file mode 100644 index 000000000..e52df6e0c --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/NHDr0.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : NHDr0.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // Info from http://www.geocities.jp/t98next/nhdr0.txt + public partial class Nhdr0 : IWritableImage + { + ImageInfo imageInfo; + Nhdr0Header nhdhdr; + IFilter nhdImageFilter; + FileStream writingStream; + + public Nhdr0() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/Properties.cs b/DiscImageChef.DiscImages/NHDr0/Properties.cs new file mode 100644 index 000000000..36e91187e --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/Properties.cs @@ -0,0 +1,72 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Nhdr0 + { + public string Name => "T98-Next NHD r0 Disk Image"; + public Guid Id => new Guid("6ECACD0A-8F4D-4465-8815-AEA000D370E3"); + public ImageInfo Info => imageInfo; + + public string Format => "NHDr0 disk image"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => new[] {MediaType.GENERIC_HDD, MediaType.Unknown}; + // TODO: Support dynamic images + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".nhd"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/Read.cs b/DiscImageChef.DiscImages/NHDr0/Read.cs new file mode 100644 index 000000000..d8224f2b2 --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/Read.cs @@ -0,0 +1,106 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Nhdr0 + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p + Encoding shiftjis = Encoding.GetEncoding("shift_jis"); + + nhdhdr = new Nhdr0Header(); + + if(stream.Length < Marshal.SizeOf(nhdhdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(nhdhdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + nhdhdr = (Nhdr0Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Nhdr0Header)); + handle.Free(); + + imageInfo.MediaType = MediaType.GENERIC_HDD; + + imageInfo.ImageSize = (ulong)(stream.Length - nhdhdr.dwHeadSize); + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = (ulong)(nhdhdr.dwCylinder * nhdhdr.wHead * nhdhdr.wSect); + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.SectorSize = (uint)nhdhdr.wSectLen; + imageInfo.Cylinders = (uint)nhdhdr.dwCylinder; + imageInfo.Heads = (uint)nhdhdr.wHead; + imageInfo.SectorsPerTrack = (uint)nhdhdr.wSect; + imageInfo.Comments = StringHandlers.CToString(nhdhdr.szComment, shiftjis); + + nhdImageFilter = imageFilter; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Stream stream = nhdImageFilter.GetDataForkStream(); + + stream.Seek((long)((ulong)nhdhdr.dwHeadSize + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/Structs.cs b/DiscImageChef.DiscImages/NHDr0/Structs.cs new file mode 100644 index 000000000..d9f05df6b --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/Structs.cs @@ -0,0 +1,58 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Nhdr0 + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Nhdr0Header + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public byte[] szFileID; + public byte reserved1; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] + public byte[] szComment; + public int dwHeadSize; + public int dwCylinder; + public short wHead; + public short wSect; + public short wSectLen; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] reserved2; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xE0)] + public byte[] reserved3; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/Unsupported.cs b/DiscImageChef.DiscImages/NHDr0/Unsupported.cs new file mode 100644 index 000000000..1a187bf11 --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Nhdr0 + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/NHDr0/Write.cs b/DiscImageChef.DiscImages/NHDr0/Write.cs new file mode 100644 index 000000000..cdda9615a --- /dev/null +++ b/DiscImageChef.DiscImages/NHDr0/Write.cs @@ -0,0 +1,282 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes NHD r0 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Nhdr0 + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 0) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream + .Seek((long)((ulong)Marshal.SizeOf(typeof(Nhdr0Header)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream + .Seek((long)((ulong)Marshal.SizeOf(typeof(Nhdr0Header)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(imageInfo.Cylinders == 0) + { + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 8 / 17); + imageInfo.Heads = 8; + imageInfo.SectorsPerTrack = 17; + + while(imageInfo.Cylinders == 0) + { + imageInfo.Heads--; + + if(imageInfo.Heads == 0) + { + imageInfo.SectorsPerTrack--; + imageInfo.Heads = 16; + } + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); + + if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; + } + } + + Nhdr0Header header = new Nhdr0Header + { + szFileID = signature, + szComment = new byte[0x100], + dwHeadSize = Marshal.SizeOf(typeof(Nhdr0Header)), + dwCylinder = (byte)imageInfo.Cylinders, + wHead = (byte)imageInfo.Heads, + wSect = (byte)imageInfo.SectorsPerTrack, + wSectLen = (byte)imageInfo.SectorSize, + reserved2 = new byte[2], + reserved3 = new byte[0xE0] + }; + + if(!string.IsNullOrEmpty(imageInfo.Comments)) + { + byte[] commentBytes = Encoding.GetEncoding("shift_jis").GetBytes(imageInfo.Comments); + Array.Copy(commentBytes, 0, header.szComment, 0, + commentBytes.Length >= 0x100 ? 0x100 : commentBytes.Length); + } + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.StructureToPtr(header, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + IsWriting = false; + + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.Comments = metadata.Comments; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > int.MaxValue) + { + ErrorMessage = "Too many cylinders."; + return false; + } + + if(heads > short.MaxValue) + { + ErrorMessage = "Too many heads."; + return false; + } + + if(sectorsPerTrack > short.MaxValue) + { + ErrorMessage = "Too many sectors per track."; + return false; + } + + imageInfo.SectorsPerTrack = sectorsPerTrack; + imageInfo.Heads = heads; + imageInfo.Cylinders = cylinders; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero/Constants.cs b/DiscImageChef.DiscImages/Nero/Constants.cs new file mode 100644 index 000000000..f7072202e --- /dev/null +++ b/DiscImageChef.DiscImages/Nero/Constants.cs @@ -0,0 +1,53 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Nero Burning ROM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Nero + { + const uint NERO_FOOTER_V1 = 0x4E45524F; // "NERO" + const uint NERO_FOOTER_V2 = 0x4E455235; // "NER5" + const uint NERO_CUE_V1 = 0x43554553; // "CUES" + const uint NERO_CUE_V2 = 0x43554558; // "CUEX" + const uint NERO_TAO_V1 = 0x45544E46; // "ETNF" + const uint NERO_TAO_V2 = 0x45544E32; // "ETN2" + const uint NERO_DAO_V1 = 0x44414F49; // "DAOI" + const uint NERO_DAO_V2 = 0x44414F58; // "DAOX" + const uint NERO_CDTEXT = 0x43445458; // "CDTX" + const uint NERO_SESSION = 0x53494E46; // "SINF" + const uint NERO_DISC_TYPE = 0x4D545950; // "MTYP" + const uint NERO_DISC_INFO = 0x44494E46; // "DINF" + const uint NERO_TOC = 0x544F4354; // "TOCT" + const uint NERO_RELOCATION = 0x52454C4F; // "RELO" + const uint NERO_END = 0x454E4421; // "END!" + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero/Enums.cs b/DiscImageChef.DiscImages/Nero/Enums.cs new file mode 100644 index 000000000..9d3c8c30b --- /dev/null +++ b/DiscImageChef.DiscImages/Nero/Enums.cs @@ -0,0 +1,223 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Nero Burning ROM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace DiscImageChef.DiscImages +{ + public partial class Nero + { + enum DaoMode : ushort + { + Data = 0x0000, + DataM2F1 = 0x0002, + DataM2F2 = 0x0003, + DataRaw = 0x0005, + DataM2Raw = 0x0006, + Audio = 0x0007, + DataRawSub = 0x000F, + AudioSub = 0x0010, + DataM2RawSub = 0x0011 + } + + [Flags] + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum NeroMediaTypes : uint + { + /// <summary> + /// No media + /// </summary> + NeroMtypNone = 0x00000, + /// <summary> + /// CD-R/RW + /// </summary> + NeroMtypCd = 0x00001, + /// <summary> + /// DDCD-R/RW + /// </summary> + NeroMtypDdcd = 0x00002, + /// <summary> + /// DVD-R/RW + /// </summary> + NeroMtypDvdM = 0x00004, + /// <summary> + /// DVD+RW + /// </summary> + NeroMtypDvdP = 0x00008, + /// <summary> + /// DVD-RAM + /// </summary> + NeroMtypDvdRam = 0x00010, + /// <summary> + /// Multi-level disc + /// </summary> + NeroMtypMl = 0x00020, + /// <summary> + /// Mount Rainier + /// </summary> + NeroMtypMrw = 0x00040, + /// <summary> + /// Exclude CD-R + /// </summary> + NeroMtypNoCdr = 0x00080, + /// <summary> + /// Exclude CD-RW + /// </summary> + NeroMtypNoCdrw = 0x00100, + /// <summary> + /// CD-RW + /// </summary> + NeroMtypCdrw = NeroMtypCd | NeroMtypNoCdr, + /// <summary> + /// CD-R + /// </summary> + NeroMtypCdr = NeroMtypCd | NeroMtypNoCdrw, + /// <summary> + /// DVD-ROM + /// </summary> + NeroMtypDvdRom = 0x00200, + /// <summary> + /// CD-ROM + /// </summary> + NeroMtypCdrom = 0x00400, + /// <summary> + /// Exclude DVD-RW + /// </summary> + NeroMtypNoDvdMRw = 0x00800, + /// <summary> + /// Exclude DVD-R + /// </summary> + NeroMtypNoDvdMR = 0x01000, + /// <summary> + /// Exclude DVD+RW + /// </summary> + NeroMtypNoDvdPRw = 0x02000, + /// <summary> + /// Exclude DVD+R + /// </summary> + NeroMtypNoDvdPR = 0x04000, + /// <summary> + /// DVD-R + /// </summary> + NeroMtypDvdMR = NeroMtypDvdM | NeroMtypNoDvdMRw, + /// <summary> + /// DVD-RW + /// </summary> + NeroMtypDvdMRw = NeroMtypDvdM | NeroMtypNoDvdMR, + /// <summary> + /// DVD+R + /// </summary> + NeroMtypDvdPR = NeroMtypDvdP | NeroMtypNoDvdPRw, + /// <summary> + /// DVD+RW + /// </summary> + NeroMtypDvdPRw = NeroMtypDvdP | NeroMtypNoDvdPR, + /// <summary> + /// Packet-writing (fixed) + /// </summary> + NeroMtypFpacket = 0x08000, + /// <summary> + /// Packet-writing (variable) + /// </summary> + NeroMtypVpacket = 0x10000, + /// <summary> + /// Packet-writing (any) + /// </summary> + NeroMtypPacketw = NeroMtypMrw | NeroMtypFpacket | NeroMtypVpacket, + /// <summary> + /// HD-Burn + /// </summary> + NeroMtypHdb = 0x20000, + /// <summary> + /// DVD+R DL + /// </summary> + NeroMtypDvdPR9 = 0x40000, + /// <summary> + /// DVD-R DL + /// </summary> + NeroMtypDvdMR9 = 0x80000, + /// <summary> + /// Any DVD double-layer + /// </summary> + NeroMtypDvdAnyR9 = NeroMtypDvdPR9 | NeroMtypDvdMR9, + /// <summary> + /// Any DVD + /// </summary> + NeroMtypDvdAny = NeroMtypDvdM | NeroMtypDvdP | NeroMtypDvdRam | NeroMtypDvdAnyR9, + /// <summary> + /// BD-ROM + /// </summary> + NeroMtypBdRom = 0x100000, + /// <summary> + /// BD-R + /// </summary> + NeroMtypBdR = 0x200000, + /// <summary> + /// BD-RE + /// </summary> + NeroMtypBdRe = 0x400000, + /// <summary> + /// BD-R/RE + /// </summary> + NeroMtypBd = NeroMtypBdR | NeroMtypBdRe, + /// <summary> + /// Any BD + /// </summary> + NeroMtypBdAny = NeroMtypBd | NeroMtypBdRom, + /// <summary> + /// HD DVD-ROM + /// </summary> + NeroMtypHdDvdRom = 0x0800000, + /// <summary> + /// HD DVD-R + /// </summary> + NeroMtypHdDvdR = 0x1000000, + /// <summary> + /// HD DVD-RW + /// </summary> + NeroMtypHdDvdRw = 0x2000000, + /// <summary> + /// HD DVD-R/RW + /// </summary> + NeroMtypHdDvd = NeroMtypHdDvdR | NeroMtypHdDvdRw, + /// <summary> + /// Any HD DVD + /// </summary> + NeroMtypHdDvdAny = NeroMtypHdDvd | NeroMtypHdDvdRom, + /// <summary> + /// Any DVD, old + /// </summary> + NeroMtypDvdAnyOld = NeroMtypDvdM | NeroMtypDvdP | NeroMtypDvdRam + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero/Helpers.cs b/DiscImageChef.DiscImages/Nero/Helpers.cs new file mode 100644 index 000000000..81499ca85 --- /dev/null +++ b/DiscImageChef.DiscImages/Nero/Helpers.cs @@ -0,0 +1,111 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Nero Burning ROM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; + +namespace DiscImageChef.DiscImages +{ + public partial class Nero + { + static MediaType NeroMediaTypeToMediaType(NeroMediaTypes type) + { + switch(type) + { + case NeroMediaTypes.NeroMtypDdcd: return MediaType.DDCD; + case NeroMediaTypes.NeroMtypDvdM: + case NeroMediaTypes.NeroMtypDvdMR: return MediaType.DVDR; + case NeroMediaTypes.NeroMtypDvdP: + case NeroMediaTypes.NeroMtypDvdPR: return MediaType.DVDPR; + case NeroMediaTypes.NeroMtypDvdRam: return MediaType.DVDRAM; + case NeroMediaTypes.NeroMtypMl: + case NeroMediaTypes.NeroMtypMrw: + case NeroMediaTypes.NeroMtypCdrw: return MediaType.CDRW; + case NeroMediaTypes.NeroMtypCdr: return MediaType.CDR; + case NeroMediaTypes.NeroMtypDvdRom: + case NeroMediaTypes.NeroMtypDvdAny: + case NeroMediaTypes.NeroMtypDvdAnyR9: + case NeroMediaTypes.NeroMtypDvdAnyOld: return MediaType.DVDROM; + case NeroMediaTypes.NeroMtypCdrom: return MediaType.CDROM; + case NeroMediaTypes.NeroMtypDvdMRw: return MediaType.DVDRW; + case NeroMediaTypes.NeroMtypDvdPRw: return MediaType.DVDPRW; + case NeroMediaTypes.NeroMtypDvdPR9: return MediaType.DVDPRDL; + case NeroMediaTypes.NeroMtypDvdMR9: return MediaType.DVDRDL; + case NeroMediaTypes.NeroMtypBd: + case NeroMediaTypes.NeroMtypBdAny: + case NeroMediaTypes.NeroMtypBdRom: return MediaType.BDROM; + case NeroMediaTypes.NeroMtypBdR: return MediaType.BDR; + case NeroMediaTypes.NeroMtypBdRe: return MediaType.BDRE; + case NeroMediaTypes.NeroMtypHdDvd: + case NeroMediaTypes.NeroMtypHdDvdAny: + case NeroMediaTypes.NeroMtypHdDvdRom: return MediaType.HDDVDROM; + case NeroMediaTypes.NeroMtypHdDvdR: return MediaType.HDDVDR; + case NeroMediaTypes.NeroMtypHdDvdRw: return MediaType.HDDVDRW; + default: return MediaType.CD; + } + } + + static TrackType NeroTrackModeToTrackType(DaoMode mode) + { + switch(mode) + { + case DaoMode.Data: + case DaoMode.DataRaw: + case DaoMode.DataRawSub: return TrackType.CdMode1; + case DaoMode.DataM2F1: return TrackType.CdMode2Form1; + case DaoMode.DataM2F2: return TrackType.CdMode2Form2; + case DaoMode.DataM2RawSub: + case DaoMode.DataM2Raw: return TrackType.CdMode2Formless; + case DaoMode.Audio: + case DaoMode.AudioSub: return TrackType.Audio; + default: return TrackType.Data; + } + } + + static ushort NeroTrackModeToBytesPerSector(DaoMode mode) + { + switch(mode) + { + case DaoMode.Data: + case DaoMode.DataM2F1: return 2048; + case DaoMode.DataM2F2: return 2336; + case DaoMode.DataRaw: + case DaoMode.DataM2Raw: + case DaoMode.Audio: return 2352; + case DaoMode.DataM2RawSub: + case DaoMode.DataRawSub: + case DaoMode.AudioSub: return 2448; + default: return 2352; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero/Identify.cs b/DiscImageChef.DiscImages/Nero/Identify.cs new file mode 100644 index 000000000..da4744bca --- /dev/null +++ b/DiscImageChef.DiscImages/Nero/Identify.cs @@ -0,0 +1,73 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Nero Burning ROM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Nero + { + public bool Identify(IFilter imageFilter) + { + imageStream = imageFilter.GetDataForkStream(); + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + NeroV1Footer footerV1 = new NeroV1Footer(); + NeroV2Footer footerV2 = new NeroV2Footer(); + + imageStream.Seek(-8, SeekOrigin.End); + byte[] buffer = new byte[8]; + imageStream.Read(buffer, 0, 8); + footerV1.ChunkId = BigEndianBitConverter.ToUInt32(buffer, 0); + footerV1.FirstChunkOffset = BigEndianBitConverter.ToUInt32(buffer, 4); + + imageStream.Seek(-12, SeekOrigin.End); + buffer = new byte[12]; + imageStream.Read(buffer, 0, 12); + footerV2.ChunkId = BigEndianBitConverter.ToUInt32(buffer, 0); + footerV2.FirstChunkOffset = BigEndianBitConverter.ToUInt64(buffer, 4); + + DicConsole.DebugWriteLine("Nero plugin", "imageStream.Length = {0}", imageStream.Length); + DicConsole.DebugWriteLine("Nero plugin", "footerV1.ChunkID = 0x{0:X8}", footerV1.ChunkId); + DicConsole.DebugWriteLine("Nero plugin", "footerV1.FirstChunkOffset = {0}", footerV1.FirstChunkOffset); + DicConsole.DebugWriteLine("Nero plugin", "footerV2.ChunkID = 0x{0:X8}", footerV2.ChunkId); + DicConsole.DebugWriteLine("Nero plugin", "footerV2.FirstChunkOffset = {0}", footerV2.FirstChunkOffset); + + if(footerV2.ChunkId == NERO_FOOTER_V2 && footerV2.FirstChunkOffset < (ulong)imageStream.Length) return true; + + return footerV1.ChunkId == NERO_FOOTER_V1 && footerV1.FirstChunkOffset < (ulong)imageStream.Length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero/Nero.cs b/DiscImageChef.DiscImages/Nero/Nero.cs new file mode 100644 index 000000000..77d6e85cc --- /dev/null +++ b/DiscImageChef.DiscImages/Nero/Nero.cs @@ -0,0 +1,86 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Nero.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Nero Burning ROM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +#pragma warning disable 414 +#pragma warning disable 169 + +namespace DiscImageChef.DiscImages +{ + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + [SuppressMessage("ReSharper", "CollectionNeverQueried.Local")] + public partial class Nero : IMediaImage + { + bool imageNewFormat; + Stream imageStream; + ImageInfo imageInfo; + NeroCdText neroCdtxt; + NeroV1Cuesheet neroCuesheetV1; + NeroV2Cuesheet neroCuesheetV2; + NeroV1Dao neroDaov1; + NeroV2Dao neroDaov2; + NeroDiscInformation neroDiscInfo; + IFilter neroFilter; + NeroMediaType neroMediaTyp; + NeroReloChunk neroRelo; + Dictionary<ushort, uint> neroSessions; + NeroV1Tao neroTaov1; + NeroV2Tao neroTaov2; + NeroTocChunk neroToc; + Dictionary<uint, NeroTrack> neroTracks; + Dictionary<uint, ulong> offsetmap; + Dictionary<uint, byte[]> trackIsrCs; + byte[] upc; + + public Nero() + { + imageNewFormat = false; + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>() + }; + neroSessions = new Dictionary<ushort, uint>(); + neroTracks = new Dictionary<uint, NeroTrack>(); + offsetmap = new Dictionary<uint, ulong>(); + Sessions = new List<Session>(); + Partitions = new List<Partition>(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero/Properties.cs b/DiscImageChef.DiscImages/Nero/Properties.cs new file mode 100644 index 000000000..9d8f4c20a --- /dev/null +++ b/DiscImageChef.DiscImages/Nero/Properties.cs @@ -0,0 +1,53 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Nero Burning ROM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Nero + { + public ImageInfo Info => imageInfo; + public string Name => "Nero Burning ROM image"; + public Guid Id => new Guid("D160F9FF-5941-43FC-B037-AD81DD141F05"); + public string Format => "Nero Burning ROM"; + public List<Partition> Partitions { get; } + public List<Track> Tracks { get; private set; } + public List<Session> Sessions { get; } + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero.cs b/DiscImageChef.DiscImages/Nero/Read.cs similarity index 73% rename from DiscImageChef.DiscImages/Nero.cs rename to DiscImageChef.DiscImages/Nero/Read.cs index 741a86507..6d16399ba 100644 --- a/DiscImageChef.DiscImages/Nero.cs +++ b/DiscImageChef.DiscImages/Nero/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : Nero.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Nero Burning ROM disc images. +// Reads Nero Burning ROM disc images. // // --[ License ] -------------------------------------------------------------- // @@ -32,7 +32,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -43,136 +42,11 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; -using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; - -#pragma warning disable 414 -#pragma warning disable 169 namespace DiscImageChef.DiscImages { - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - [SuppressMessage("ReSharper", "CollectionNeverQueried.Local")] - public class Nero : IMediaImage + public partial class Nero { - // "NERO" - const uint NERO_FOOTER_V1 = 0x4E45524F; - - // "NER5" - const uint NERO_FOOTER_V2 = 0x4E455235; - - // "CUES" - const uint NERO_CUE_V1 = 0x43554553; - - // "CUEX" - const uint NERO_CUE_V2 = 0x43554558; - - // "ETNF" - const uint NERO_TAO_V1 = 0x45544E46; - - // "ETN2" - const uint NERO_TAO_V2 = 0x45544E32; - - // "DAOI" - const uint NERO_DAO_V1 = 0x44414F49; - - // "DAOX" - const uint NERO_DAO_V2 = 0x44414F58; - - // "CDTX" - const uint NERO_CDTEXT = 0x43445458; - - // "SINF" - const uint NERO_SESSION = 0x53494E46; - - // "MTYP" - const uint NERO_DISC_TYPE = 0x4D545950; - - // "DINF" - const uint NERO_DISC_INFO = 0x44494E46; - - // "TOCT" - const uint NERO_TOC = 0x544F4354; - - // "RELO" - const uint NERO_RELOCATION = 0x52454C4F; - - // "END!" - const uint NERO_END = 0x454E4421; - bool imageNewFormat; - Stream imageStream; - ImageInfo imageInfo; - public ImageInfo Info => imageInfo; - - NeroCdText neroCdtxt; - NeroV1Cuesheet neroCuesheetV1; - NeroV2Cuesheet neroCuesheetV2; - NeroV1Dao neroDaov1; - NeroV2Dao neroDaov2; - NeroDiscInformation neroDiscInfo; - - IFilter neroFilter; - NeroMediaType neroMediaTyp; - NeroReloChunk neroRelo; - Dictionary<ushort, uint> neroSessions; - NeroV1Tao neroTaov1; - NeroV2Tao neroTaov2; - NeroTocChunk neroToc; - Dictionary<uint, NeroTrack> neroTracks; - Dictionary<uint, ulong> offsetmap; - Dictionary<uint, byte[]> trackIsrCs; - byte[] upc; - - public string Name => "Nero Burning ROM image"; - public Guid Id => new Guid("D160F9FF-5941-43FC-B037-AD81DD141F05"); - - public Nero() - { - imageNewFormat = false; - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>() - }; - neroSessions = new Dictionary<ushort, uint>(); - neroTracks = new Dictionary<uint, NeroTrack>(); - offsetmap = new Dictionary<uint, ulong>(); - Sessions = new List<Session>(); - Partitions = new List<Partition>(); - } - - // Due to .cue format, this method must parse whole file, ignoring errors (those will be thrown by OpenImage()). - public bool Identify(IFilter imageFilter) - { - imageStream = imageFilter.GetDataForkStream(); - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - NeroV1Footer footerV1 = new NeroV1Footer(); - NeroV2Footer footerV2 = new NeroV2Footer(); - - imageStream.Seek(-8, SeekOrigin.End); - byte[] buffer = new byte[8]; - imageStream.Read(buffer, 0, 8); - footerV1.ChunkId = BigEndianBitConverter.ToUInt32(buffer, 0); - footerV1.FirstChunkOffset = BigEndianBitConverter.ToUInt32(buffer, 4); - - imageStream.Seek(-12, SeekOrigin.End); - buffer = new byte[12]; - imageStream.Read(buffer, 0, 12); - footerV2.ChunkId = BigEndianBitConverter.ToUInt32(buffer, 0); - footerV2.FirstChunkOffset = BigEndianBitConverter.ToUInt64(buffer, 4); - - DicConsole.DebugWriteLine("Nero plugin", "imageStream.Length = {0}", imageStream.Length); - DicConsole.DebugWriteLine("Nero plugin", "footerV1.ChunkID = 0x{0:X8}", footerV1.ChunkId); - DicConsole.DebugWriteLine("Nero plugin", "footerV1.FirstChunkOffset = {0}", footerV1.FirstChunkOffset); - DicConsole.DebugWriteLine("Nero plugin", "footerV2.ChunkID = 0x{0:X8}", footerV2.ChunkId); - DicConsole.DebugWriteLine("Nero plugin", "footerV2.FirstChunkOffset = {0}", footerV2.FirstChunkOffset); - - if(footerV2.ChunkId == NERO_FOOTER_V2 && footerV2.FirstChunkOffset < (ulong)imageStream.Length) return true; - - return footerV1.ChunkId == NERO_FOOTER_V1 && footerV1.FirstChunkOffset < (ulong)imageStream.Length; - } - public bool Open(IFilter imageFilter) { try @@ -1520,12 +1394,6 @@ namespace DiscImageChef.DiscImages return buffer; } - public string Format => "Nero Burning ROM"; - - public List<Partition> Partitions { get; } - - public List<Track> Tracks { get; private set; } - public List<Track> GetSessionTracks(Session session) { return GetSessionTracks(session.SessionSequence); @@ -1536,8 +1404,6 @@ namespace DiscImageChef.DiscImages return Tracks.Where(track => track.TrackSession == session).ToList(); } - public List<Session> Sessions { get; } - public bool? VerifySector(ulong sectorAddress) { byte[] buffer = ReadSectorLong(sectorAddress); @@ -1614,819 +1480,5 @@ namespace DiscImageChef.DiscImages { return null; } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - static MediaType NeroMediaTypeToMediaType(NeroMediaTypes type) - { - switch(type) - { - case NeroMediaTypes.NeroMtypDdcd: return MediaType.DDCD; - case NeroMediaTypes.NeroMtypDvdM: - case NeroMediaTypes.NeroMtypDvdMR: return MediaType.DVDR; - case NeroMediaTypes.NeroMtypDvdP: - case NeroMediaTypes.NeroMtypDvdPR: return MediaType.DVDPR; - case NeroMediaTypes.NeroMtypDvdRam: return MediaType.DVDRAM; - case NeroMediaTypes.NeroMtypMl: - case NeroMediaTypes.NeroMtypMrw: - case NeroMediaTypes.NeroMtypCdrw: return MediaType.CDRW; - case NeroMediaTypes.NeroMtypCdr: return MediaType.CDR; - case NeroMediaTypes.NeroMtypDvdRom: - case NeroMediaTypes.NeroMtypDvdAny: - case NeroMediaTypes.NeroMtypDvdAnyR9: - case NeroMediaTypes.NeroMtypDvdAnyOld: return MediaType.DVDROM; - case NeroMediaTypes.NeroMtypCdrom: return MediaType.CDROM; - case NeroMediaTypes.NeroMtypDvdMRw: return MediaType.DVDRW; - case NeroMediaTypes.NeroMtypDvdPRw: return MediaType.DVDPRW; - case NeroMediaTypes.NeroMtypDvdPR9: return MediaType.DVDPRDL; - case NeroMediaTypes.NeroMtypDvdMR9: return MediaType.DVDRDL; - case NeroMediaTypes.NeroMtypBd: - case NeroMediaTypes.NeroMtypBdAny: - case NeroMediaTypes.NeroMtypBdRom: return MediaType.BDROM; - case NeroMediaTypes.NeroMtypBdR: return MediaType.BDR; - case NeroMediaTypes.NeroMtypBdRe: return MediaType.BDRE; - case NeroMediaTypes.NeroMtypHdDvd: - case NeroMediaTypes.NeroMtypHdDvdAny: - case NeroMediaTypes.NeroMtypHdDvdRom: return MediaType.HDDVDROM; - case NeroMediaTypes.NeroMtypHdDvdR: return MediaType.HDDVDR; - case NeroMediaTypes.NeroMtypHdDvdRw: return MediaType.HDDVDRW; - default: return MediaType.CD; - } - } - - static TrackType NeroTrackModeToTrackType(DaoMode mode) - { - switch(mode) - { - case DaoMode.Data: - case DaoMode.DataRaw: - case DaoMode.DataRawSub: return TrackType.CdMode1; - case DaoMode.DataM2F1: return TrackType.CdMode2Form1; - case DaoMode.DataM2F2: return TrackType.CdMode2Form2; - case DaoMode.DataM2RawSub: - case DaoMode.DataM2Raw: return TrackType.CdMode2Formless; - case DaoMode.Audio: - case DaoMode.AudioSub: return TrackType.Audio; - default: return TrackType.Data; - } - } - - static ushort NeroTrackModeToBytesPerSector(DaoMode mode) - { - switch(mode) - { - case DaoMode.Data: - case DaoMode.DataM2F1: return 2048; - case DaoMode.DataM2F2: return 2336; - case DaoMode.DataRaw: - case DaoMode.DataM2Raw: - case DaoMode.Audio: return 2352; - case DaoMode.DataM2RawSub: - case DaoMode.DataRawSub: - case DaoMode.AudioSub: return 2448; - default: return 2352; - } - } - - struct NeroV1Footer - { - /// <summary> - /// "NERO" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Offset of first chunk in file - /// </summary> - public uint FirstChunkOffset; - } - - struct NeroV2Footer - { - /// <summary> - /// "NER5" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Offset of first chunk in file - /// </summary> - public ulong FirstChunkOffset; - } - - struct NeroV2CueEntry - { - /// <summary> - /// Track mode. 0x01 for audio, 0x21 for copy-protected audio, 0x41 for data - /// </summary> - public byte Mode; - - /// <summary> - /// Track number in BCD - /// </summary> - public byte TrackNumber; - - /// <summary> - /// Index number in BCD - /// </summary> - public byte IndexNumber; - - /// <summary> - /// Always zero - /// </summary> - public byte Dummy; - - /// <summary> - /// LBA sector start for this entry - /// </summary> - public int LbaStart; - } - - struct NeroV2Cuesheet - { - /// <summary> - /// "CUEX" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// Cuesheet entries - /// </summary> - public List<NeroV2CueEntry> Entries; - } - - struct NeroV1CueEntry - { - /// <summary> - /// Track mode. 0x01 for audio, 0x21 for copy-protected audio, 0x41 for data - /// </summary> - public byte Mode; - - /// <summary> - /// Track number in BCD - /// </summary> - public byte TrackNumber; - - /// <summary> - /// Index number in BCD - /// </summary> - public byte IndexNumber; - - /// <summary> - /// Always zero - /// </summary> - public ushort Dummy; - - /// <summary> - /// MSF start sector's minute for this entry - /// </summary> - public byte Minute; - - /// <summary> - /// MSF start sector's second for this entry - /// </summary> - public byte Second; - - /// <summary> - /// MSF start sector's frame for this entry - /// </summary> - public byte Frame; - } - - struct NeroV1Cuesheet - { - /// <summary> - /// "CUES" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// Cuesheet entries - /// </summary> - public List<NeroV1CueEntry> Entries; - } - - struct NeroV1DaoEntry - { - /// <summary> - /// ISRC (12 bytes) - /// </summary> - public byte[] Isrc; - - /// <summary> - /// Size of sector inside image (in bytes) - /// </summary> - public ushort SectorSize; - - /// <summary> - /// Sector mode in image - /// </summary> - public ushort Mode; - - /// <summary> - /// Unknown - /// </summary> - public ushort Unknown; - - /// <summary> - /// Index 0 start - /// </summary> - public uint Index0; - - /// <summary> - /// Index 1 start - /// </summary> - public uint Index1; - - /// <summary> - /// End of track + 1 - /// </summary> - public uint EndOfTrack; - } - - struct NeroV1Dao - { - /// <summary> - /// "DAOI" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size (big endian) - /// </summary> - public uint ChunkSizeBe; - - /// <summary> - /// Chunk size (little endian) - /// </summary> - public uint ChunkSizeLe; - - /// <summary> - /// UPC (14 bytes, null-padded) - /// </summary> - public byte[] Upc; - - /// <summary> - /// TOC type - /// </summary> - public ushort TocType; - - /// <summary> - /// First track - /// </summary> - public byte FirstTrack; - - /// <summary> - /// Last track - /// </summary> - public byte LastTrack; - - /// <summary> - /// Tracks - /// </summary> - public List<NeroV1DaoEntry> Tracks; - } - - struct NeroV2DaoEntry - { - /// <summary> - /// ISRC (12 bytes) - /// </summary> - public byte[] Isrc; - - /// <summary> - /// Size of sector inside image (in bytes) - /// </summary> - public ushort SectorSize; - - /// <summary> - /// Sector mode in image - /// </summary> - public ushort Mode; - - /// <summary> - /// Seems to be always 0. - /// </summary> - public ushort Unknown; - - /// <summary> - /// Index 0 start - /// </summary> - public ulong Index0; - - /// <summary> - /// Index 1 start - /// </summary> - public ulong Index1; - - /// <summary> - /// End of track + 1 - /// </summary> - public ulong EndOfTrack; - } - - struct NeroV2Dao - { - /// <summary> - /// "DAOX" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size (big endian) - /// </summary> - public uint ChunkSizeBe; - - /// <summary> - /// Chunk size (little endian) - /// </summary> - public uint ChunkSizeLe; - - /// <summary> - /// UPC (14 bytes, null-padded) - /// </summary> - public byte[] Upc; - - /// <summary> - /// TOC type - /// </summary> - public ushort TocType; - - /// <summary> - /// First track - /// </summary> - public byte FirstTrack; - - /// <summary> - /// Last track - /// </summary> - public byte LastTrack; - - /// <summary> - /// Tracks - /// </summary> - public List<NeroV2DaoEntry> Tracks; - } - - struct NeroCdTextPack - { - /// <summary> - /// Pack type - /// </summary> - public byte PackType; - - /// <summary> - /// Track number - /// </summary> - public byte TrackNumber; - - /// <summary> - /// Pack number in block - /// </summary> - public byte PackNumber; - - /// <summary> - /// Block number - /// </summary> - public byte BlockNumber; - - /// <summary> - /// 12 bytes of data - /// </summary> - public byte[] Text; - - /// <summary> - /// CRC - /// </summary> - public ushort Crc; - } - - struct NeroCdText - { - /// <summary> - /// "CDTX" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// CD-TEXT packs - /// </summary> - public List<NeroCdTextPack> Packs; - } - - struct NeroV1TaoEntry - { - /// <summary> - /// Offset of track on image - /// </summary> - public uint Offset; - - /// <summary> - /// Length of track in bytes - /// </summary> - public uint Length; - - /// <summary> - /// Track mode - /// </summary> - public uint Mode; - - /// <summary> - /// LBA track start (plus 150 lead in sectors) - /// </summary> - public uint StartLba; - - /// <summary> - /// Unknown - /// </summary> - public uint Unknown; - } - - struct NeroV1Tao - { - /// <summary> - /// "ETNF" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// CD-TEXT packs - /// </summary> - public List<NeroV1TaoEntry> Tracks; - } - - struct NeroV2TaoEntry - { - /// <summary> - /// Offset of track on image - /// </summary> - public ulong Offset; - - /// <summary> - /// Length of track in bytes - /// </summary> - public ulong Length; - - /// <summary> - /// Track mode - /// </summary> - public uint Mode; - - /// <summary> - /// LBA track start (plus 150 lead in sectors) - /// </summary> - public uint StartLba; - - /// <summary> - /// Unknown - /// </summary> - public uint Unknown; - - /// <summary> - /// Track length in sectors - /// </summary> - public uint Sectors; - } - - struct NeroV2Tao - { - /// <summary> - /// "ETN2" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// CD-TEXT packs - /// </summary> - public List<NeroV2TaoEntry> Tracks; - } - - struct NeroSession - { - /// <summary> - /// "SINF" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// Tracks in session - /// </summary> - public uint Tracks; - } - - struct NeroMediaType - { - /// <summary> - /// "MTYP" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// Media type - /// </summary> - public uint Type; - } - - struct NeroDiscInformation - { - /// <summary> - /// "DINF" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// Unknown - /// </summary> - public uint Unknown; - } - - struct NeroTocChunk - { - /// <summary> - /// "TOCT" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// Unknown - /// </summary> - public ushort Unknown; - } - - struct NeroReloChunk - { - /// <summary> - /// "RELO" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - - /// <summary> - /// Unknown - /// </summary> - public uint Unknown; - } - - struct NeroEndOfChunkChain - { - /// <summary> - /// "END!" - /// </summary> - public uint ChunkId; - - /// <summary> - /// Chunk size - /// </summary> - public uint ChunkSize; - } - - // Internal use only - struct NeroTrack - { - public byte[] Isrc; - public ushort SectorSize; - public ulong Offset; - public ulong Length; - public ulong EndOfTrack; - public uint Mode; - public ulong StartLba; - public ulong Sectors; - public ulong Index0; - public ulong Index1; - public uint Sequence; - } - - enum DaoMode : ushort - { - Data = 0x0000, - DataM2F1 = 0x0002, - DataM2F2 = 0x0003, - DataRaw = 0x0005, - DataM2Raw = 0x0006, - Audio = 0x0007, - DataRawSub = 0x000F, - AudioSub = 0x0010, - DataM2RawSub = 0x0011 - } - - [Flags] - [SuppressMessage("ReSharper", "InconsistentNaming")] - enum NeroMediaTypes : uint - { - /// <summary> - /// No media - /// </summary> - NeroMtypNone = 0x00000, - /// <summary> - /// CD-R/RW - /// </summary> - NeroMtypCd = 0x00001, - /// <summary> - /// DDCD-R/RW - /// </summary> - NeroMtypDdcd = 0x00002, - /// <summary> - /// DVD-R/RW - /// </summary> - NeroMtypDvdM = 0x00004, - /// <summary> - /// DVD+RW - /// </summary> - NeroMtypDvdP = 0x00008, - /// <summary> - /// DVD-RAM - /// </summary> - NeroMtypDvdRam = 0x00010, - /// <summary> - /// Multi-level disc - /// </summary> - NeroMtypMl = 0x00020, - /// <summary> - /// Mount Rainier - /// </summary> - NeroMtypMrw = 0x00040, - /// <summary> - /// Exclude CD-R - /// </summary> - NeroMtypNoCdr = 0x00080, - /// <summary> - /// Exclude CD-RW - /// </summary> - NeroMtypNoCdrw = 0x00100, - /// <summary> - /// CD-RW - /// </summary> - NeroMtypCdrw = NeroMtypCd | NeroMtypNoCdr, - /// <summary> - /// CD-R - /// </summary> - NeroMtypCdr = NeroMtypCd | NeroMtypNoCdrw, - /// <summary> - /// DVD-ROM - /// </summary> - NeroMtypDvdRom = 0x00200, - /// <summary> - /// CD-ROM - /// </summary> - NeroMtypCdrom = 0x00400, - /// <summary> - /// Exclude DVD-RW - /// </summary> - NeroMtypNoDvdMRw = 0x00800, - /// <summary> - /// Exclude DVD-R - /// </summary> - NeroMtypNoDvdMR = 0x01000, - /// <summary> - /// Exclude DVD+RW - /// </summary> - NeroMtypNoDvdPRw = 0x02000, - /// <summary> - /// Exclude DVD+R - /// </summary> - NeroMtypNoDvdPR = 0x04000, - /// <summary> - /// DVD-R - /// </summary> - NeroMtypDvdMR = NeroMtypDvdM | NeroMtypNoDvdMRw, - /// <summary> - /// DVD-RW - /// </summary> - NeroMtypDvdMRw = NeroMtypDvdM | NeroMtypNoDvdMR, - /// <summary> - /// DVD+R - /// </summary> - NeroMtypDvdPR = NeroMtypDvdP | NeroMtypNoDvdPRw, - /// <summary> - /// DVD+RW - /// </summary> - NeroMtypDvdPRw = NeroMtypDvdP | NeroMtypNoDvdPR, - /// <summary> - /// Packet-writing (fixed) - /// </summary> - NeroMtypFpacket = 0x08000, - /// <summary> - /// Packet-writing (variable) - /// </summary> - NeroMtypVpacket = 0x10000, - /// <summary> - /// Packet-writing (any) - /// </summary> - NeroMtypPacketw = NeroMtypMrw | NeroMtypFpacket | NeroMtypVpacket, - /// <summary> - /// HD-Burn - /// </summary> - NeroMtypHdb = 0x20000, - /// <summary> - /// DVD+R DL - /// </summary> - NeroMtypDvdPR9 = 0x40000, - /// <summary> - /// DVD-R DL - /// </summary> - NeroMtypDvdMR9 = 0x80000, - /// <summary> - /// Any DVD double-layer - /// </summary> - NeroMtypDvdAnyR9 = NeroMtypDvdPR9 | NeroMtypDvdMR9, - /// <summary> - /// Any DVD - /// </summary> - NeroMtypDvdAny = NeroMtypDvdM | NeroMtypDvdP | NeroMtypDvdRam | NeroMtypDvdAnyR9, - /// <summary> - /// BD-ROM - /// </summary> - NeroMtypBdRom = 0x100000, - /// <summary> - /// BD-R - /// </summary> - NeroMtypBdR = 0x200000, - /// <summary> - /// BD-RE - /// </summary> - NeroMtypBdRe = 0x400000, - /// <summary> - /// BD-R/RE - /// </summary> - NeroMtypBd = NeroMtypBdR | NeroMtypBdRe, - /// <summary> - /// Any BD - /// </summary> - NeroMtypBdAny = NeroMtypBd | NeroMtypBdRom, - /// <summary> - /// HD DVD-ROM - /// </summary> - NeroMtypHdDvdRom = 0x0800000, - /// <summary> - /// HD DVD-R - /// </summary> - NeroMtypHdDvdR = 0x1000000, - /// <summary> - /// HD DVD-RW - /// </summary> - NeroMtypHdDvdRw = 0x2000000, - /// <summary> - /// HD DVD-R/RW - /// </summary> - NeroMtypHdDvd = NeroMtypHdDvdR | NeroMtypHdDvdRw, - /// <summary> - /// Any HD DVD - /// </summary> - NeroMtypHdDvdAny = NeroMtypHdDvd | NeroMtypHdDvdRom, - /// <summary> - /// Any DVD, old - /// </summary> - NeroMtypDvdAnyOld = NeroMtypDvdM | NeroMtypDvdP | NeroMtypDvdRam - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Nero/Structs.cs b/DiscImageChef.DiscImages/Nero/Structs.cs new file mode 100644 index 000000000..c35a42340 --- /dev/null +++ b/DiscImageChef.DiscImages/Nero/Structs.cs @@ -0,0 +1,596 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Nero Burning ROM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; + +namespace DiscImageChef.DiscImages +{ + public partial class Nero + { + struct NeroV1Footer + { + /// <summary> + /// "NERO" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Offset of first chunk in file + /// </summary> + public uint FirstChunkOffset; + } + + struct NeroV2Footer + { + /// <summary> + /// "NER5" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Offset of first chunk in file + /// </summary> + public ulong FirstChunkOffset; + } + + struct NeroV2CueEntry + { + /// <summary> + /// Track mode. 0x01 for audio, 0x21 for copy-protected audio, 0x41 for data + /// </summary> + public byte Mode; + + /// <summary> + /// Track number in BCD + /// </summary> + public byte TrackNumber; + + /// <summary> + /// Index number in BCD + /// </summary> + public byte IndexNumber; + + /// <summary> + /// Always zero + /// </summary> + public byte Dummy; + + /// <summary> + /// LBA sector start for this entry + /// </summary> + public int LbaStart; + } + + struct NeroV2Cuesheet + { + /// <summary> + /// "CUEX" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// Cuesheet entries + /// </summary> + public List<NeroV2CueEntry> Entries; + } + + struct NeroV1CueEntry + { + /// <summary> + /// Track mode. 0x01 for audio, 0x21 for copy-protected audio, 0x41 for data + /// </summary> + public byte Mode; + + /// <summary> + /// Track number in BCD + /// </summary> + public byte TrackNumber; + + /// <summary> + /// Index number in BCD + /// </summary> + public byte IndexNumber; + + /// <summary> + /// Always zero + /// </summary> + public ushort Dummy; + + /// <summary> + /// MSF start sector's minute for this entry + /// </summary> + public byte Minute; + + /// <summary> + /// MSF start sector's second for this entry + /// </summary> + public byte Second; + + /// <summary> + /// MSF start sector's frame for this entry + /// </summary> + public byte Frame; + } + + struct NeroV1Cuesheet + { + /// <summary> + /// "CUES" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// Cuesheet entries + /// </summary> + public List<NeroV1CueEntry> Entries; + } + + struct NeroV1DaoEntry + { + /// <summary> + /// ISRC (12 bytes) + /// </summary> + public byte[] Isrc; + + /// <summary> + /// Size of sector inside image (in bytes) + /// </summary> + public ushort SectorSize; + + /// <summary> + /// Sector mode in image + /// </summary> + public ushort Mode; + + /// <summary> + /// Unknown + /// </summary> + public ushort Unknown; + + /// <summary> + /// Index 0 start + /// </summary> + public uint Index0; + + /// <summary> + /// Index 1 start + /// </summary> + public uint Index1; + + /// <summary> + /// End of track + 1 + /// </summary> + public uint EndOfTrack; + } + + struct NeroV1Dao + { + /// <summary> + /// "DAOI" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size (big endian) + /// </summary> + public uint ChunkSizeBe; + + /// <summary> + /// Chunk size (little endian) + /// </summary> + public uint ChunkSizeLe; + + /// <summary> + /// UPC (14 bytes, null-padded) + /// </summary> + public byte[] Upc; + + /// <summary> + /// TOC type + /// </summary> + public ushort TocType; + + /// <summary> + /// First track + /// </summary> + public byte FirstTrack; + + /// <summary> + /// Last track + /// </summary> + public byte LastTrack; + + /// <summary> + /// Tracks + /// </summary> + public List<NeroV1DaoEntry> Tracks; + } + + struct NeroV2DaoEntry + { + /// <summary> + /// ISRC (12 bytes) + /// </summary> + public byte[] Isrc; + + /// <summary> + /// Size of sector inside image (in bytes) + /// </summary> + public ushort SectorSize; + + /// <summary> + /// Sector mode in image + /// </summary> + public ushort Mode; + + /// <summary> + /// Seems to be always 0. + /// </summary> + public ushort Unknown; + + /// <summary> + /// Index 0 start + /// </summary> + public ulong Index0; + + /// <summary> + /// Index 1 start + /// </summary> + public ulong Index1; + + /// <summary> + /// End of track + 1 + /// </summary> + public ulong EndOfTrack; + } + + struct NeroV2Dao + { + /// <summary> + /// "DAOX" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size (big endian) + /// </summary> + public uint ChunkSizeBe; + + /// <summary> + /// Chunk size (little endian) + /// </summary> + public uint ChunkSizeLe; + + /// <summary> + /// UPC (14 bytes, null-padded) + /// </summary> + public byte[] Upc; + + /// <summary> + /// TOC type + /// </summary> + public ushort TocType; + + /// <summary> + /// First track + /// </summary> + public byte FirstTrack; + + /// <summary> + /// Last track + /// </summary> + public byte LastTrack; + + /// <summary> + /// Tracks + /// </summary> + public List<NeroV2DaoEntry> Tracks; + } + + struct NeroCdTextPack + { + /// <summary> + /// Pack type + /// </summary> + public byte PackType; + + /// <summary> + /// Track number + /// </summary> + public byte TrackNumber; + + /// <summary> + /// Pack number in block + /// </summary> + public byte PackNumber; + + /// <summary> + /// Block number + /// </summary> + public byte BlockNumber; + + /// <summary> + /// 12 bytes of data + /// </summary> + public byte[] Text; + + /// <summary> + /// CRC + /// </summary> + public ushort Crc; + } + + struct NeroCdText + { + /// <summary> + /// "CDTX" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// CD-TEXT packs + /// </summary> + public List<NeroCdTextPack> Packs; + } + + struct NeroV1TaoEntry + { + /// <summary> + /// Offset of track on image + /// </summary> + public uint Offset; + + /// <summary> + /// Length of track in bytes + /// </summary> + public uint Length; + + /// <summary> + /// Track mode + /// </summary> + public uint Mode; + + /// <summary> + /// LBA track start (plus 150 lead in sectors) + /// </summary> + public uint StartLba; + + /// <summary> + /// Unknown + /// </summary> + public uint Unknown; + } + + struct NeroV1Tao + { + /// <summary> + /// "ETNF" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// CD-TEXT packs + /// </summary> + public List<NeroV1TaoEntry> Tracks; + } + + struct NeroV2TaoEntry + { + /// <summary> + /// Offset of track on image + /// </summary> + public ulong Offset; + + /// <summary> + /// Length of track in bytes + /// </summary> + public ulong Length; + + /// <summary> + /// Track mode + /// </summary> + public uint Mode; + + /// <summary> + /// LBA track start (plus 150 lead in sectors) + /// </summary> + public uint StartLba; + + /// <summary> + /// Unknown + /// </summary> + public uint Unknown; + + /// <summary> + /// Track length in sectors + /// </summary> + public uint Sectors; + } + + struct NeroV2Tao + { + /// <summary> + /// "ETN2" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// CD-TEXT packs + /// </summary> + public List<NeroV2TaoEntry> Tracks; + } + + struct NeroSession + { + /// <summary> + /// "SINF" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// Tracks in session + /// </summary> + public uint Tracks; + } + + struct NeroMediaType + { + /// <summary> + /// "MTYP" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// Media type + /// </summary> + public uint Type; + } + + struct NeroDiscInformation + { + /// <summary> + /// "DINF" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// Unknown + /// </summary> + public uint Unknown; + } + + struct NeroTocChunk + { + /// <summary> + /// "TOCT" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// Unknown + /// </summary> + public ushort Unknown; + } + + struct NeroReloChunk + { + /// <summary> + /// "RELO" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + + /// <summary> + /// Unknown + /// </summary> + public uint Unknown; + } + + struct NeroEndOfChunkChain + { + /// <summary> + /// "END!" + /// </summary> + public uint ChunkId; + + /// <summary> + /// Chunk size + /// </summary> + public uint ChunkSize; + } + + // Internal use only + struct NeroTrack + { + public byte[] Isrc; + public ushort SectorSize; + public ulong Offset; + public ulong Length; + public ulong EndOfTrack; + public uint Mode; + public ulong StartLba; + public ulong Sectors; + public ulong Index0; + public ulong Index1; + public uint Sequence; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels.cs b/DiscImageChef.DiscImages/Parallels.cs deleted file mode 100644 index e15cc46cd..000000000 --- a/DiscImageChef.DiscImages/Parallels.cs +++ /dev/null @@ -1,670 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : Parallels.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Parallels disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class Parallels : IWritableImage - { - const uint PARALLELS_VERSION = 2; - - const uint PARALLELS_INUSE = 0x746F6E59; - const uint PARALLELS_CLOSED = 0x312E3276; - - const uint PARALLELS_EMPTY = 0x00000001; - - const uint MAX_CACHE_SIZE = 16777216; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; - const uint DEFAULT_CLUSTER_SIZE = 1048576; - readonly byte[] parallelsExtMagic = - {0x57, 0x69, 0x74, 0x68, 0x6F, 0x75, 0x46, 0x72, 0x65, 0x53, 0x70, 0x61, 0x63, 0x45, 0x78, 0x74}; - readonly byte[] parallelsMagic = - {0x57, 0x69, 0x74, 0x68, 0x6F, 0x75, 0x74, 0x46, 0x72, 0x65, 0x65, 0x53, 0x70, 0x61, 0x63, 0x65}; - uint[] bat; - uint clusterBytes; - long currentWritingPosition; - long dataOffset; - bool empty; - - bool extended; - ImageInfo imageInfo; - Stream imageStream; - ParallelsHeader pHdr; - - Dictionary<ulong, byte[]> sectorCache; - FileStream writingStream; - - public Parallels() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = "2", - Application = "Parallels", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "Parallels disk image"; - public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); - public ImageInfo Info => imageInfo; - - public string Format => "Parallels"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; - stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); - pHdr = new ParallelsHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); - Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); - pHdr = (ParallelsHeader)Marshal.PtrToStructure(headerPtr, typeof(ParallelsHeader)); - Marshal.FreeHGlobal(headerPtr); - - return parallelsMagic.SequenceEqual(pHdr.magic) || parallelsExtMagic.SequenceEqual(pHdr.magic); - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; - stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); - pHdr = new ParallelsHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); - Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); - pHdr = (ParallelsHeader)Marshal.PtrToStructure(headerPtr, typeof(ParallelsHeader)); - Marshal.FreeHGlobal(headerPtr); - - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.magic = {0}", - StringHandlers.CToString(pHdr.magic)); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.version = {0}", pHdr.version); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.heads = {0}", pHdr.heads); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.cylinders = {0}", pHdr.cylinders); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.cluster_size = {0}", pHdr.cluster_size); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.bat_entries = {0}", pHdr.bat_entries); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.sectors = {0}", pHdr.sectors); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.in_use = 0x{0:X8}", pHdr.in_use); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.data_off = {0}", pHdr.data_off); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.flags = {0}", pHdr.flags); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.ext_off = {0}", pHdr.ext_off); - - extended = parallelsExtMagic.SequenceEqual(pHdr.magic); - DicConsole.DebugWriteLine("Parallels plugin", "pHdr.extended = {0}", extended); - - DicConsole.DebugWriteLine("Parallels plugin", "Reading BAT"); - bat = new uint[pHdr.bat_entries]; - byte[] batB = new byte[pHdr.bat_entries * 4]; - stream.Read(batB, 0, batB.Length); - for(int i = 0; i < bat.Length; i++) bat[i] = BitConverter.ToUInt32(batB, i * 4); - - clusterBytes = pHdr.cluster_size * 512; - if(pHdr.data_off > 0) dataOffset = pHdr.data_off * 512; - else - dataOffset = - (stream.Position / clusterBytes + stream.Position % clusterBytes) * clusterBytes; - - sectorCache = new Dictionary<ulong, byte[]>(); - - empty = (pHdr.flags & PARALLELS_EMPTY) == PARALLELS_EMPTY; - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = pHdr.sectors; - imageInfo.SectorSize = 512; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.ImageSize = pHdr.sectors * 512; - imageInfo.Cylinders = pHdr.cylinders; - imageInfo.Heads = pHdr.heads; - imageInfo.SectorsPerTrack = (uint)(imageInfo.Sectors / imageInfo.Cylinders / imageInfo.Heads); - imageStream = stream; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(empty) return new byte[512]; - - if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - - ulong index = sectorAddress / pHdr.cluster_size; - ulong secOff = sectorAddress % pHdr.cluster_size; - - uint batOff = bat[index]; - ulong imageOff; - - if(batOff == 0) return new byte[512]; - - if(extended) imageOff = batOff * clusterBytes; - else imageOff = batOff * 512; - - byte[] cluster = new byte[clusterBytes]; - imageStream.Seek((long)imageOff, SeekOrigin.Begin); - imageStream.Read(cluster, 0, (int)clusterBytes); - sector = new byte[512]; - Array.Copy(cluster, (int)(secOff * 512), sector, 0, 512); - - if(sectorCache.Count > MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - - return sector; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - if(empty) return new byte[512 * length]; - - MemoryStream ms = new MemoryStream(); - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - // TODO: Add cluster size option - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".hdd"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - // TODO: Support extended - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - if(sectors * sectorSize / DEFAULT_CLUSTER_SIZE > uint.MaxValue) - { - ErrorMessage = "Too many sectors for selected cluster size"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - uint batEntries = (uint)(sectors * sectorSize / DEFAULT_CLUSTER_SIZE); - if(sectors * sectorSize % DEFAULT_CLUSTER_SIZE > 0) batEntries++; - uint headerSectors = (uint)Marshal.SizeOf(typeof(ParallelsHeader)) + batEntries * 4; - if((uint)Marshal.SizeOf(typeof(ParallelsHeader)) + batEntries % 4 > 0) headerSectors++; - - pHdr = new ParallelsHeader - { - magic = parallelsMagic, - version = PARALLELS_VERSION, - sectors = sectors, - in_use = PARALLELS_CLOSED, - bat_entries = batEntries, - data_off = headerSectors, - cluster_size = DEFAULT_CLUSTER_SIZE / 512 - }; - - bat = new uint[batEntries]; - currentWritingPosition = headerSectors * 512; - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - ulong index = sectorAddress / pHdr.cluster_size; - ulong secOff = sectorAddress % pHdr.cluster_size; - - uint batOff = bat[index]; - - if(batOff == 0) - { - batOff = (uint)(currentWritingPosition / 512); - bat[index] = batOff; - currentWritingPosition += pHdr.cluster_size * 512; - } - - ulong imageOff = batOff * 512; - - writingStream.Seek((long)imageOff, SeekOrigin.Begin); - writingStream.Seek((long)secOff * 512, SeekOrigin.Current); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - // TODO: This can be optimized - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - for(uint i = 0; i < length; i++) - { - byte[] tmp = new byte[512]; - Array.Copy(data, i * 512, tmp, 0, 512); - if(!WriteSector(tmp, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(pHdr.cylinders == 0) - { - pHdr.cylinders = (uint)(imageInfo.Sectors / 16 / 63); - pHdr.heads = 16; - imageInfo.SectorsPerTrack = 63; - - while(pHdr.cylinders == 0) - { - pHdr.heads--; - - if(pHdr.heads == 0) - { - imageInfo.SectorsPerTrack--; - pHdr.heads = 16; - } - - pHdr.cylinders = (uint)(imageInfo.Sectors / pHdr.heads / imageInfo.SectorsPerTrack); - - if(pHdr.cylinders == 0 && pHdr.heads == 0 && imageInfo.SectorsPerTrack == 0) break; - } - } - - byte[] hdr = new byte[Marshal.SizeOf(typeof(ParallelsHeader))]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ParallelsHeader))); - Marshal.StructureToPtr(pHdr, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - for(long i = 0; i < bat.LongLength; i++) writingStream.Write(BitConverter.GetBytes(bat[i]), 0, 4); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - pHdr.cylinders = cylinders; - pHdr.heads = heads; - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - /// <summary> - /// Parallels disk image header, little-endian - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ParallelsHeader - { - /// <summary> - /// Magic, <see cref="Parallels.parallelsMagic" /> or <see cref="Parallels.parallelsExtMagic" /> - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] magic; - /// <summary> - /// Version - /// </summary> - public uint version; - /// <summary> - /// Disk geometry parameter - /// </summary> - public uint heads; - /// <summary> - /// Disk geometry parameter - /// </summary> - public uint cylinders; - /// <summary> - /// Cluser size in sectors - /// </summary> - public uint cluster_size; - /// <summary> - /// Entries in BAT (clusters in image) - /// </summary> - public uint bat_entries; - /// <summary> - /// Disk size in sectors - /// </summary> - public ulong sectors; - /// <summary> - /// Set to <see cref="Parallels.PARALLELS_INUSE" /> if image is opened by any software, - /// <see cref="Parallels.PARALLELS_CLOSED" /> if not, and 0 if old version - /// </summary> - public uint in_use; - /// <summary> - /// Offset in sectors to start of data - /// </summary> - public uint data_off; - /// <summary> - /// Flags - /// </summary> - public uint flags; - /// <summary> - /// Offset in sectors to format extension - /// </summary> - public ulong ext_off; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Constants.cs b/DiscImageChef.DiscImages/Parallels/Constants.cs new file mode 100644 index 000000000..b6b1f8ca6 --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Constants.cs @@ -0,0 +1,49 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels + { + const uint PARALLELS_VERSION = 2; + const uint PARALLELS_INUSE = 0x746F6E59; + const uint PARALLELS_CLOSED = 0x312E3276; + const uint PARALLELS_EMPTY = 0x00000001; + const uint MAX_CACHE_SIZE = 16777216; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; + const uint DEFAULT_CLUSTER_SIZE = 1048576; + readonly byte[] parallelsExtMagic = + {0x57, 0x69, 0x74, 0x68, 0x6F, 0x75, 0x46, 0x72, 0x65, 0x53, 0x70, 0x61, 0x63, 0x45, 0x78, 0x74}; + readonly byte[] parallelsMagic = + {0x57, 0x69, 0x74, 0x68, 0x6F, 0x75, 0x74, 0x46, 0x72, 0x65, 0x65, 0x53, 0x70, 0x61, 0x63, 0x65}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Identify.cs b/DiscImageChef.DiscImages/Parallels/Identify.cs new file mode 100644 index 000000000..851df8319 --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Identify.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; + stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); + pHdr = new ParallelsHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); + Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); + pHdr = (ParallelsHeader)Marshal.PtrToStructure(headerPtr, typeof(ParallelsHeader)); + Marshal.FreeHGlobal(headerPtr); + + return parallelsMagic.SequenceEqual(pHdr.magic) || parallelsExtMagic.SequenceEqual(pHdr.magic); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Parallels.cs b/DiscImageChef.DiscImages/Parallels/Parallels.cs new file mode 100644 index 000000000..7f614dc84 --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Parallels.cs @@ -0,0 +1,82 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Parallels.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels : IWritableImage + { + uint[] bat; + uint clusterBytes; + long currentWritingPosition; + long dataOffset; + bool empty; + bool extended; + ImageInfo imageInfo; + Stream imageStream; + ParallelsHeader pHdr; + Dictionary<ulong, byte[]> sectorCache; + FileStream writingStream; + + public Parallels() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = "2", + Application = "Parallels", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Properties.cs b/DiscImageChef.DiscImages/Parallels/Properties.cs new file mode 100644 index 000000000..f77a63791 --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Properties.cs @@ -0,0 +1,78 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels + { + public string Name => "Parallels disk image"; + public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); + public ImageInfo Info => imageInfo; + + public string Format => "Parallels"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + // TODO: Add cluster size option + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".hdd"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Read.cs b/DiscImageChef.DiscImages/Parallels/Read.cs new file mode 100644 index 000000000..a0e46dbb6 --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Read.cs @@ -0,0 +1,166 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; + stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); + pHdr = new ParallelsHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); + Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); + pHdr = (ParallelsHeader)Marshal.PtrToStructure(headerPtr, typeof(ParallelsHeader)); + Marshal.FreeHGlobal(headerPtr); + + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.magic = {0}", + StringHandlers.CToString(pHdr.magic)); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.version = {0}", pHdr.version); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.heads = {0}", pHdr.heads); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.cylinders = {0}", pHdr.cylinders); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.cluster_size = {0}", pHdr.cluster_size); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.bat_entries = {0}", pHdr.bat_entries); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.sectors = {0}", pHdr.sectors); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.in_use = 0x{0:X8}", pHdr.in_use); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.data_off = {0}", pHdr.data_off); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.flags = {0}", pHdr.flags); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.ext_off = {0}", pHdr.ext_off); + + extended = parallelsExtMagic.SequenceEqual(pHdr.magic); + DicConsole.DebugWriteLine("Parallels plugin", "pHdr.extended = {0}", extended); + + DicConsole.DebugWriteLine("Parallels plugin", "Reading BAT"); + bat = new uint[pHdr.bat_entries]; + byte[] batB = new byte[pHdr.bat_entries * 4]; + stream.Read(batB, 0, batB.Length); + for(int i = 0; i < bat.Length; i++) bat[i] = BitConverter.ToUInt32(batB, i * 4); + + clusterBytes = pHdr.cluster_size * 512; + if(pHdr.data_off > 0) dataOffset = pHdr.data_off * 512; + else + dataOffset = + (stream.Position / clusterBytes + stream.Position % clusterBytes) * clusterBytes; + + sectorCache = new Dictionary<ulong, byte[]>(); + + empty = (pHdr.flags & PARALLELS_EMPTY) == PARALLELS_EMPTY; + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = pHdr.sectors; + imageInfo.SectorSize = 512; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.ImageSize = pHdr.sectors * 512; + imageInfo.Cylinders = pHdr.cylinders; + imageInfo.Heads = pHdr.heads; + imageInfo.SectorsPerTrack = (uint)(imageInfo.Sectors / imageInfo.Cylinders / imageInfo.Heads); + imageStream = stream; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(empty) return new byte[512]; + + if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; + + ulong index = sectorAddress / pHdr.cluster_size; + ulong secOff = sectorAddress % pHdr.cluster_size; + + uint batOff = bat[index]; + ulong imageOff; + + if(batOff == 0) return new byte[512]; + + if(extended) imageOff = batOff * clusterBytes; + else imageOff = batOff * 512; + + byte[] cluster = new byte[clusterBytes]; + imageStream.Seek((long)imageOff, SeekOrigin.Begin); + imageStream.Read(cluster, 0, (int)clusterBytes); + sector = new byte[512]; + Array.Copy(cluster, (int)(secOff * 512), sector, 0, 512); + + if(sectorCache.Count > MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + if(empty) return new byte[512 * length]; + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Structs.cs b/DiscImageChef.DiscImages/Parallels/Structs.cs new file mode 100644 index 000000000..c8aa5e65e --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Structs.cs @@ -0,0 +1,93 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels + { + /// <summary> + /// Parallels disk image header, little-endian + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ParallelsHeader + { + /// <summary> + /// Magic, <see cref="Parallels.parallelsMagic" /> or <see cref="Parallels.parallelsExtMagic" /> + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] magic; + /// <summary> + /// Version + /// </summary> + public uint version; + /// <summary> + /// Disk geometry parameter + /// </summary> + public uint heads; + /// <summary> + /// Disk geometry parameter + /// </summary> + public uint cylinders; + /// <summary> + /// Cluser size in sectors + /// </summary> + public uint cluster_size; + /// <summary> + /// Entries in BAT (clusters in image) + /// </summary> + public uint bat_entries; + /// <summary> + /// Disk size in sectors + /// </summary> + public ulong sectors; + /// <summary> + /// Set to <see cref="Parallels.PARALLELS_INUSE" /> if image is opened by any software, + /// <see cref="Parallels.PARALLELS_CLOSED" /> if not, and 0 if old version + /// </summary> + public uint in_use; + /// <summary> + /// Offset in sectors to start of data + /// </summary> + public uint data_off; + /// <summary> + /// Flags + /// </summary> + public uint flags; + /// <summary> + /// Offset in sectors to format extension + /// </summary> + public ulong ext_off; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Unsupported.cs b/DiscImageChef.DiscImages/Parallels/Unsupported.cs new file mode 100644 index 000000000..c37991191 --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Parallels/Write.cs b/DiscImageChef.DiscImages/Parallels/Write.cs new file mode 100644 index 000000000..dd3aecada --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels/Write.cs @@ -0,0 +1,291 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Parallels disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Parallels + { + // TODO: Support extended + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + if(sectors * sectorSize / DEFAULT_CLUSTER_SIZE > uint.MaxValue) + { + ErrorMessage = "Too many sectors for selected cluster size"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + uint batEntries = (uint)(sectors * sectorSize / DEFAULT_CLUSTER_SIZE); + if(sectors * sectorSize % DEFAULT_CLUSTER_SIZE > 0) batEntries++; + uint headerSectors = (uint)Marshal.SizeOf(typeof(ParallelsHeader)) + batEntries * 4; + if((uint)Marshal.SizeOf(typeof(ParallelsHeader)) + batEntries % 4 > 0) headerSectors++; + + pHdr = new ParallelsHeader + { + magic = parallelsMagic, + version = PARALLELS_VERSION, + sectors = sectors, + in_use = PARALLELS_CLOSED, + bat_entries = batEntries, + data_off = headerSectors, + cluster_size = DEFAULT_CLUSTER_SIZE / 512 + }; + + bat = new uint[batEntries]; + currentWritingPosition = headerSectors * 512; + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + ulong index = sectorAddress / pHdr.cluster_size; + ulong secOff = sectorAddress % pHdr.cluster_size; + + uint batOff = bat[index]; + + if(batOff == 0) + { + batOff = (uint)(currentWritingPosition / 512); + bat[index] = batOff; + currentWritingPosition += pHdr.cluster_size * 512; + } + + ulong imageOff = batOff * 512; + + writingStream.Seek((long)imageOff, SeekOrigin.Begin); + writingStream.Seek((long)secOff * 512, SeekOrigin.Current); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + // TODO: This can be optimized + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + for(uint i = 0; i < length; i++) + { + byte[] tmp = new byte[512]; + Array.Copy(data, i * 512, tmp, 0, 512); + if(!WriteSector(tmp, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(pHdr.cylinders == 0) + { + pHdr.cylinders = (uint)(imageInfo.Sectors / 16 / 63); + pHdr.heads = 16; + imageInfo.SectorsPerTrack = 63; + + while(pHdr.cylinders == 0) + { + pHdr.heads--; + + if(pHdr.heads == 0) + { + imageInfo.SectorsPerTrack--; + pHdr.heads = 16; + } + + pHdr.cylinders = (uint)(imageInfo.Sectors / pHdr.heads / imageInfo.SectorsPerTrack); + + if(pHdr.cylinders == 0 && pHdr.heads == 0 && imageInfo.SectorsPerTrack == 0) break; + } + } + + byte[] hdr = new byte[Marshal.SizeOf(typeof(ParallelsHeader))]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ParallelsHeader))); + Marshal.StructureToPtr(pHdr, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + for(long i = 0; i < bat.LongLength; i++) writingStream.Write(BitConverter.GetBytes(bat[i]), 0, 4); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + pHdr.cylinders = cylinders; + pHdr.heads = heads; + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone.cs b/DiscImageChef.DiscImages/PartClone.cs deleted file mode 100644 index 98e4f3497..000000000 --- a/DiscImageChef.DiscImages/PartClone.cs +++ /dev/null @@ -1,435 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : PartClone.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages partclone disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Extents; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class PartClone : IMediaImage - { - const int CRC_SIZE = 4; - - const uint MAX_CACHE_SIZE = 16777216; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; - readonly byte[] biTmAgIc = {0x42, 0x69, 0x54, 0x6D, 0x41, 0x67, 0x49, 0x63}; - readonly byte[] partCloneMagic = - {0x70, 0x61, 0x72, 0x74, 0x63, 0x6C, 0x6F, 0x6E, 0x65, 0x2D, 0x69, 0x6D, 0x61, 0x67, 0x65}; - // The used block "bitmap" uses one byte per block - // TODO: Convert on-image bytemap to on-memory bitmap - byte[] byteMap; - long dataOff; - - ExtentsULong extents; - Dictionary<ulong, ulong> extentsOff; - ImageInfo imageInfo; - Stream imageStream; - - PartCloneHeader pHdr; - - Dictionary<ulong, byte[]> sectorCache; - - public PartClone() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Application = "PartClone", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "PartClone disk image"; - public Guid Id => new Guid("AB1D7518-B548-4099-A4E2-C29C53DDE0C3"); - public ImageInfo Info => imageInfo; - - public string Format => "PartClone"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; - stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); - pHdr = new PartCloneHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); - Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); - pHdr = (PartCloneHeader)Marshal.PtrToStructure(headerPtr, typeof(PartCloneHeader)); - Marshal.FreeHGlobal(headerPtr); - - if(stream.Position + (long)pHdr.totalBlocks > stream.Length) return false; - - stream.Seek((long)pHdr.totalBlocks, SeekOrigin.Current); - - byte[] bitmagic = new byte[8]; - stream.Read(bitmagic, 0, 8); - - return partCloneMagic.SequenceEqual(pHdr.magic) && biTmAgIc.SequenceEqual(bitmagic); - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; - stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); - pHdr = new PartCloneHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); - Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); - pHdr = (PartCloneHeader)Marshal.PtrToStructure(headerPtr, typeof(PartCloneHeader)); - Marshal.FreeHGlobal(headerPtr); - - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.magic = {0}", StringHandlers.CToString(pHdr.magic)); - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.filesystem = {0}", - StringHandlers.CToString(pHdr.filesystem)); - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.version = {0}", - StringHandlers.CToString(pHdr.version)); - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.blockSize = {0}", pHdr.blockSize); - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.deviceSize = {0}", pHdr.deviceSize); - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.totalBlocks = {0}", pHdr.totalBlocks); - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.usedBlocks = {0}", pHdr.usedBlocks); - - byteMap = new byte[pHdr.totalBlocks]; - DicConsole.DebugWriteLine("PartClone plugin", "Reading bytemap {0} bytes", byteMap.Length); - stream.Read(byteMap, 0, byteMap.Length); - - byte[] bitmagic = new byte[8]; - stream.Read(bitmagic, 0, 8); - - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.bitmagic = {0}", StringHandlers.CToString(bitmagic)); - - if(!biTmAgIc.SequenceEqual(bitmagic)) - throw new ImageNotSupportedException("Could not find partclone BiTmAgIc, not continuing..."); - - dataOff = stream.Position; - DicConsole.DebugWriteLine("PartClone plugin", "pHdr.dataOff = {0}", dataOff); - - DicConsole.DebugWriteLine("PartClone plugin", "Filling extents"); - DateTime start = DateTime.Now; - extents = new ExtentsULong(); - extentsOff = new Dictionary<ulong, ulong>(); - bool current = byteMap[0] > 0; - ulong blockOff = 0; - ulong extentStart = 0; - - for(ulong i = 1; i < pHdr.totalBlocks; i++) - { - bool next = byteMap[i] > 0; - - // Flux - if(next != current) - if(next) - { - extentStart = i; - extentsOff.Add(i, ++blockOff); - } - else - { - extents.Add(extentStart, i); - extentsOff.TryGetValue(extentStart, out _); - } - - if(next && current) blockOff++; - - current = next; - } - - DateTime end = DateTime.Now; - DicConsole.DebugWriteLine("PartClone plugin", "Took {0} seconds to fill extents", - (end - start).TotalSeconds); - - sectorCache = new Dictionary<ulong, byte[]>(); - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = pHdr.totalBlocks; - imageInfo.SectorSize = pHdr.blockSize; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.ImageSize = (ulong)(stream.Length - (4096 + 0x40 + (long)pHdr.totalBlocks)); - imageStream = stream; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(byteMap[sectorAddress] == 0) return new byte[pHdr.blockSize]; - - if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - - long imageOff = dataOff + (long)(BlockOffset(sectorAddress) * (pHdr.blockSize + CRC_SIZE)); - - sector = new byte[pHdr.blockSize]; - imageStream.Seek(imageOff, SeekOrigin.Begin); - imageStream.Read(sector, 0, (int)pHdr.blockSize); - - if(sectorCache.Count > MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - - return sector; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream ms = new MemoryStream(); - - bool allEmpty = true; - for(uint i = 0; i < length; i++) - if(byteMap[sectorAddress + i] != 0) - { - allEmpty = false; - break; - } - - if(allEmpty) return new byte[pHdr.blockSize * length]; - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - // TODO: All blocks contain a CRC32 that's incompatible with current implementation. Need to check for compatibility. - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - ulong BlockOffset(ulong sectorAddress) - { - extents.GetStart(sectorAddress, out ulong extentStart); - extentsOff.TryGetValue(extentStart, out ulong extentStartingOffset); - return extentStartingOffset + (sectorAddress - extentStart); - } - - /// <summary> - /// PartClone disk image header, little-endian - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct PartCloneHeader - { - /// <summary> - /// Magic, <see cref="PartClone.partCloneMagic" /> - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] - public byte[] magic; - /// <summary> - /// Source filesystem - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] - public byte[] filesystem; - /// <summary> - /// Version - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public byte[] version; - /// <summary> - /// Padding - /// </summary> - public ushort padding; - /// <summary> - /// Block (sector) size - /// </summary> - public uint blockSize; - /// <summary> - /// Size of device containing the cloned partition - /// </summary> - public ulong deviceSize; - /// <summary> - /// Total blocks in cloned partition - /// </summary> - public ulong totalBlocks; - /// <summary> - /// Used blocks in cloned partition - /// </summary> - public ulong usedBlocks; - /// <summary> - /// Empty space - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] - public byte[] buffer; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/Constants.cs b/DiscImageChef.DiscImages/PartClone/Constants.cs new file mode 100644 index 000000000..e74db2844 --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/Constants.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone + { + const int CRC_SIZE = 4; + const uint MAX_CACHE_SIZE = 16777216; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; + readonly byte[] biTmAgIc = {0x42, 0x69, 0x54, 0x6D, 0x41, 0x67, 0x49, 0x63}; + readonly byte[] partCloneMagic = + {0x70, 0x61, 0x72, 0x74, 0x63, 0x6C, 0x6F, 0x6E, 0x65, 0x2D, 0x69, 0x6D, 0x61, 0x67, 0x65}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/Helpers.cs b/DiscImageChef.DiscImages/PartClone/Helpers.cs new file mode 100644 index 000000000..52df7e7cb --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/Helpers.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone + { + ulong BlockOffset(ulong sectorAddress) + { + extents.GetStart(sectorAddress, out ulong extentStart); + extentsOff.TryGetValue(extentStart, out ulong extentStartingOffset); + return extentStartingOffset + (sectorAddress - extentStart); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/Identify.cs b/DiscImageChef.DiscImages/PartClone/Identify.cs new file mode 100644 index 000000000..dd41344ab --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/Identify.cs @@ -0,0 +1,68 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; + stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); + pHdr = new PartCloneHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); + Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); + pHdr = (PartCloneHeader)Marshal.PtrToStructure(headerPtr, typeof(PartCloneHeader)); + Marshal.FreeHGlobal(headerPtr); + + if(stream.Position + (long)pHdr.totalBlocks > stream.Length) return false; + + stream.Seek((long)pHdr.totalBlocks, SeekOrigin.Current); + + byte[] bitmagic = new byte[8]; + stream.Read(bitmagic, 0, 8); + + return partCloneMagic.SequenceEqual(pHdr.magic) && biTmAgIc.SequenceEqual(bitmagic); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/PartClone.cs b/DiscImageChef.DiscImages/PartClone/PartClone.cs new file mode 100644 index 000000000..56cebac03 --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/PartClone.cs @@ -0,0 +1,81 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : PartClone.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Extents; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone : IMediaImage + { + // The used block "bitmap" uses one byte per block + // TODO: Convert on-image bytemap to on-memory bitmap + byte[] byteMap; + long dataOff; + ExtentsULong extents; + Dictionary<ulong, ulong> extentsOff; + ImageInfo imageInfo; + Stream imageStream; + PartCloneHeader pHdr; + Dictionary<ulong, byte[]> sectorCache; + + public PartClone() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Application = "PartClone", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/Properties.cs b/DiscImageChef.DiscImages/PartClone/Properties.cs new file mode 100644 index 000000000..e6f8a72c2 --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/Properties.cs @@ -0,0 +1,62 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone + { + public string Name => "PartClone disk image"; + public Guid Id => new Guid("AB1D7518-B548-4099-A4E2-C29C53DDE0C3"); + public ImageInfo Info => imageInfo; + + public string Format => "PartClone"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/Read.cs b/DiscImageChef.DiscImages/PartClone/Read.cs new file mode 100644 index 000000000..c54f643aa --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/Read.cs @@ -0,0 +1,191 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Extents; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] pHdrB = new byte[Marshal.SizeOf(pHdr)]; + stream.Read(pHdrB, 0, Marshal.SizeOf(pHdr)); + pHdr = new PartCloneHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); + Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(pHdr)); + pHdr = (PartCloneHeader)Marshal.PtrToStructure(headerPtr, typeof(PartCloneHeader)); + Marshal.FreeHGlobal(headerPtr); + + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.magic = {0}", StringHandlers.CToString(pHdr.magic)); + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.filesystem = {0}", + StringHandlers.CToString(pHdr.filesystem)); + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.version = {0}", + StringHandlers.CToString(pHdr.version)); + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.blockSize = {0}", pHdr.blockSize); + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.deviceSize = {0}", pHdr.deviceSize); + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.totalBlocks = {0}", pHdr.totalBlocks); + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.usedBlocks = {0}", pHdr.usedBlocks); + + byteMap = new byte[pHdr.totalBlocks]; + DicConsole.DebugWriteLine("PartClone plugin", "Reading bytemap {0} bytes", byteMap.Length); + stream.Read(byteMap, 0, byteMap.Length); + + byte[] bitmagic = new byte[8]; + stream.Read(bitmagic, 0, 8); + + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.bitmagic = {0}", StringHandlers.CToString(bitmagic)); + + if(!biTmAgIc.SequenceEqual(bitmagic)) + throw new ImageNotSupportedException("Could not find partclone BiTmAgIc, not continuing..."); + + dataOff = stream.Position; + DicConsole.DebugWriteLine("PartClone plugin", "pHdr.dataOff = {0}", dataOff); + + DicConsole.DebugWriteLine("PartClone plugin", "Filling extents"); + DateTime start = DateTime.Now; + extents = new ExtentsULong(); + extentsOff = new Dictionary<ulong, ulong>(); + bool current = byteMap[0] > 0; + ulong blockOff = 0; + ulong extentStart = 0; + + for(ulong i = 1; i < pHdr.totalBlocks; i++) + { + bool next = byteMap[i] > 0; + + // Flux + if(next != current) + if(next) + { + extentStart = i; + extentsOff.Add(i, ++blockOff); + } + else + { + extents.Add(extentStart, i); + extentsOff.TryGetValue(extentStart, out _); + } + + if(next && current) blockOff++; + + current = next; + } + + DateTime end = DateTime.Now; + DicConsole.DebugWriteLine("PartClone plugin", "Took {0} seconds to fill extents", + (end - start).TotalSeconds); + + sectorCache = new Dictionary<ulong, byte[]>(); + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = pHdr.totalBlocks; + imageInfo.SectorSize = pHdr.blockSize; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.ImageSize = (ulong)(stream.Length - (4096 + 0x40 + (long)pHdr.totalBlocks)); + imageStream = stream; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(byteMap[sectorAddress] == 0) return new byte[pHdr.blockSize]; + + if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; + + long imageOff = dataOff + (long)(BlockOffset(sectorAddress) * (pHdr.blockSize + CRC_SIZE)); + + sector = new byte[pHdr.blockSize]; + imageStream.Seek(imageOff, SeekOrigin.Begin); + imageStream.Read(sector, 0, (int)pHdr.blockSize); + + if(sectorCache.Count > MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + bool allEmpty = true; + for(uint i = 0; i < length; i++) + if(byteMap[sectorAddress + i] != 0) + { + allEmpty = false; + break; + } + + if(allEmpty) return new byte[pHdr.blockSize * length]; + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/Structs.cs b/DiscImageChef.DiscImages/PartClone/Structs.cs new file mode 100644 index 000000000..738d615d5 --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/Structs.cs @@ -0,0 +1,87 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone + { + /// <summary> + /// PartClone disk image header, little-endian + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct PartCloneHeader + { + /// <summary> + /// Magic, <see cref="PartClone.partCloneMagic" /> + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public byte[] magic; + /// <summary> + /// Source filesystem + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public byte[] filesystem; + /// <summary> + /// Version + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] version; + /// <summary> + /// Padding + /// </summary> + public ushort padding; + /// <summary> + /// Block (sector) size + /// </summary> + public uint blockSize; + /// <summary> + /// Size of device containing the cloned partition + /// </summary> + public ulong deviceSize; + /// <summary> + /// Total blocks in cloned partition + /// </summary> + public ulong totalBlocks; + /// <summary> + /// Used blocks in cloned partition + /// </summary> + public ulong usedBlocks; + /// <summary> + /// Empty space + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] + public byte[] buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/PartClone/Unsupported.cs b/DiscImageChef.DiscImages/PartClone/Unsupported.cs new file mode 100644 index 000000000..de1b68237 --- /dev/null +++ b/DiscImageChef.DiscImages/PartClone/Unsupported.cs @@ -0,0 +1,139 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by partclone disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class PartClone + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + // TODO: All blocks contain a CRC32 that's incompatible with current implementation. Need to check for compatibility. + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Constants.cs b/DiscImageChef.DiscImages/Partimage/Constants.cs new file mode 100644 index 000000000..c7abab64c --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Constants.cs @@ -0,0 +1,71 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage + { + const int MAX_DESCRIPTION = 4096; + const int MAX_HOSTNAMESIZE = 128; + const int MAX_DEVICENAMELEN = 512; + const int MAX_UNAMEINFOLEN = 65; //SYS_NMLN + const int MBR_SIZE_WHOLE = 512; + const int MAX_DESC_MODEL = 128; + const int MAX_DESC_GEOMETRY = 1024; + const int MAX_DESC_IDENTIFY = 4096; + const int CHECK_FREQUENCY = 65536; + const string MAGIC_BEGIN_LOCALHEADER = "MAGIC-BEGIN-LOCALHEADER"; + const string MAGIC_BEGIN_DATABLOCKS = "MAGIC-BEGIN-DATABLOCKS"; + const string MAGIC_BEGIN_BITMAP = "MAGIC-BEGIN-BITMAP"; + const string MAGIC_BEGIN_MBRBACKUP = "MAGIC-BEGIN-MBRBACKUP"; + const string MAGIC_BEGIN_TAIL = "MAGIC-BEGIN-TAIL"; + const string MAGIC_BEGIN_INFO = "MAGIC-BEGIN-INFO"; + const string MAGIC_BEGIN_EXT000 = "MAGIC-BEGIN-EXT000"; // reserved for future use + const string MAGIC_BEGIN_EXT001 = "MAGIC-BEGIN-EXT001"; // reserved for future use + const string MAGIC_BEGIN_EXT002 = "MAGIC-BEGIN-EXT002"; // reserved for future use + const string MAGIC_BEGIN_EXT003 = "MAGIC-BEGIN-EXT003"; // reserved for future use + const string MAGIC_BEGIN_EXT004 = "MAGIC-BEGIN-EXT004"; // reserved for future use + const string MAGIC_BEGIN_EXT005 = "MAGIC-BEGIN-EXT005"; // reserved for future use + const string MAGIC_BEGIN_EXT006 = "MAGIC-BEGIN-EXT006"; // reserved for future use + const string MAGIC_BEGIN_EXT007 = "MAGIC-BEGIN-EXT007"; // reserved for future use + const string MAGIC_BEGIN_EXT008 = "MAGIC-BEGIN-EXT008"; // reserved for future use + const string MAGIC_BEGIN_EXT009 = "MAGIC-BEGIN-EXT009"; // reserved for future use + const string MAGIC_BEGIN_VOLUME = "PaRtImAgE-VoLuMe"; + const uint MAX_CACHE_SIZE = 16777216; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; + readonly byte[] partimageMagic = + { + 0x50, 0x61, 0x52, 0x74, 0x49, 0x6D, 0x41, 0x67, 0x45, 0x2D, 0x56, 0x6F, 0x4C, 0x75, 0x4D, 0x65, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Enums.cs b/DiscImageChef.DiscImages/Partimage/Enums.cs new file mode 100644 index 000000000..8d019e890 --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Enums.cs @@ -0,0 +1,50 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage + { + enum PCompression : uint + { + None = 0, + Gzip = 1, + Bzip2 = 2, + Lzo = 3 + } + + enum PEncryption : uint + { + None = 0 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Helpers.cs b/DiscImageChef.DiscImages/Partimage/Helpers.cs new file mode 100644 index 000000000..f23caebb6 --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Helpers.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage + { + ulong BlockOffset(ulong sectorAddress) + { + extents.GetStart(sectorAddress, out ulong extentStart); + extentsOff.TryGetValue(extentStart, out ulong extentStartingOffset); + return extentStartingOffset + (sectorAddress - extentStart); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Identify.cs b/DiscImageChef.DiscImages/Partimage/Identify.cs new file mode 100644 index 000000000..2a70d2481 --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Identify.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] pHdrB = new byte[Marshal.SizeOf(cVolumeHeader)]; + stream.Read(pHdrB, 0, Marshal.SizeOf(cVolumeHeader)); + cVolumeHeader = new PartimageHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cVolumeHeader)); + Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(cVolumeHeader)); + cVolumeHeader = (PartimageHeader)Marshal.PtrToStructure(headerPtr, typeof(PartimageHeader)); + Marshal.FreeHGlobal(headerPtr); + + return partimageMagic.SequenceEqual(cVolumeHeader.magic); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Partimage.cs b/DiscImageChef.DiscImages/Partimage/Partimage.cs new file mode 100644 index 000000000..277cce588 --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Partimage.cs @@ -0,0 +1,82 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Partimage.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Extents; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +#pragma warning disable 649 + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage : IMediaImage + { + byte[] bitmap; + PartimageMainHeader cMainHeader; + PartimageHeader cVolumeHeader; + long dataOff; + ExtentsULong extents; + Dictionary<ulong, ulong> extentsOff; + ImageInfo imageInfo; + Stream imageStream; + Dictionary<ulong, byte[]> sectorCache; + + public Partimage() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Application = "Partimage", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Properties.cs b/DiscImageChef.DiscImages/Partimage/Properties.cs new file mode 100644 index 000000000..3116014e2 --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Properties.cs @@ -0,0 +1,63 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage + { + public ImageInfo Info => imageInfo; + + public string Name => "Partimage disk image"; + public Guid Id => new Guid("AAFDB99D-2B77-49EA-831C-C9BB58C68C95"); + + public string Format => "Partimage"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage.cs b/DiscImageChef.DiscImages/Partimage/Read.cs similarity index 55% rename from DiscImageChef.DiscImages/Partimage.cs rename to DiscImageChef.DiscImages/Partimage/Read.cs index 417cd3511..404c4d0eb 100644 --- a/DiscImageChef.DiscImages/Partimage.cs +++ b/DiscImageChef.DiscImages/Partimage/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : Partimage.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages partimage disk images. +// Reads partimage disk images. // // --[ License ] -------------------------------------------------------------- // @@ -33,130 +33,18 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Extents; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; - -#pragma warning disable 649 namespace DiscImageChef.DiscImages { - public class Partimage : IMediaImage + public partial class Partimage { - const int MAX_DESCRIPTION = 4096; - const int MAX_HOSTNAMESIZE = 128; - const int MAX_DEVICENAMELEN = 512; - const int MAX_UNAMEINFOLEN = 65; //SYS_NMLN - const int MBR_SIZE_WHOLE = 512; - const int MAX_DESC_MODEL = 128; - const int MAX_DESC_GEOMETRY = 1024; - const int MAX_DESC_IDENTIFY = 4096; - const int CHECK_FREQUENCY = 65536; - const string MAGIC_BEGIN_LOCALHEADER = "MAGIC-BEGIN-LOCALHEADER"; - const string MAGIC_BEGIN_DATABLOCKS = "MAGIC-BEGIN-DATABLOCKS"; - const string MAGIC_BEGIN_BITMAP = "MAGIC-BEGIN-BITMAP"; - const string MAGIC_BEGIN_MBRBACKUP = "MAGIC-BEGIN-MBRBACKUP"; - const string MAGIC_BEGIN_TAIL = "MAGIC-BEGIN-TAIL"; - const string MAGIC_BEGIN_INFO = "MAGIC-BEGIN-INFO"; - const string MAGIC_BEGIN_EXT000 = "MAGIC-BEGIN-EXT000"; // reserved for future use - const string MAGIC_BEGIN_EXT001 = "MAGIC-BEGIN-EXT001"; // reserved for future use - const string MAGIC_BEGIN_EXT002 = "MAGIC-BEGIN-EXT002"; // reserved for future use - const string MAGIC_BEGIN_EXT003 = "MAGIC-BEGIN-EXT003"; // reserved for future use - const string MAGIC_BEGIN_EXT004 = "MAGIC-BEGIN-EXT004"; // reserved for future use - const string MAGIC_BEGIN_EXT005 = "MAGIC-BEGIN-EXT005"; // reserved for future use - const string MAGIC_BEGIN_EXT006 = "MAGIC-BEGIN-EXT006"; // reserved for future use - const string MAGIC_BEGIN_EXT007 = "MAGIC-BEGIN-EXT007"; // reserved for future use - const string MAGIC_BEGIN_EXT008 = "MAGIC-BEGIN-EXT008"; // reserved for future use - const string MAGIC_BEGIN_EXT009 = "MAGIC-BEGIN-EXT009"; // reserved for future use - const string MAGIC_BEGIN_VOLUME = "PaRtImAgE-VoLuMe"; - - const uint MAX_CACHE_SIZE = 16777216; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; - readonly byte[] partimageMagic = - { - 0x50, 0x61, 0x52, 0x74, 0x49, 0x6D, 0x41, 0x67, 0x45, 0x2D, 0x56, 0x6F, 0x4C, 0x75, 0x4D, 0x65, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - byte[] bitmap; - PartimageMainHeader cMainHeader; - - PartimageHeader cVolumeHeader; - long dataOff; - - ExtentsULong extents; - Dictionary<ulong, ulong> extentsOff; - ImageInfo imageInfo; - Stream imageStream; - - Dictionary<ulong, byte[]> sectorCache; - - public Partimage() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Application = "Partimage", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Partimage disk image"; - public Guid Id => new Guid("AAFDB99D-2B77-49EA-831C-C9BB58C68C95"); - - public string Format => "Partimage"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] pHdrB = new byte[Marshal.SizeOf(cVolumeHeader)]; - stream.Read(pHdrB, 0, Marshal.SizeOf(cVolumeHeader)); - cVolumeHeader = new PartimageHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cVolumeHeader)); - Marshal.Copy(pHdrB, 0, headerPtr, Marshal.SizeOf(cVolumeHeader)); - cVolumeHeader = (PartimageHeader)Marshal.PtrToStructure(headerPtr, typeof(PartimageHeader)); - Marshal.FreeHGlobal(headerPtr); - - return partimageMagic.SequenceEqual(cVolumeHeader.magic); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -472,292 +360,5 @@ namespace DiscImageChef.DiscImages return ms.ToArray(); } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - // TODO: All blocks contain a CRC32 that's incompatible with current implementation. Need to check for compatibility. - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - ulong BlockOffset(ulong sectorAddress) - { - extents.GetStart(sectorAddress, out ulong extentStart); - extentsOff.TryGetValue(extentStart, out ulong extentStartingOffset); - return extentStartingOffset + (sectorAddress - extentStart); - } - - /// <summary> - /// Partimage disk image header, little-endian - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct PartimageHeader - { - /// <summary> - /// Magic, <see cref="Partimage.partimageMagic" /> - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] magic; - /// <summary> - /// Source filesystem - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] - public byte[] version; - /// <summary> - /// Volume number - /// </summary> - public uint volumeNumber; - /// <summary> - /// Image identifier - /// </summary> - public ulong identificator; - /// <summary> - /// Empty space - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 404)] - public byte[] reserved; - } - - struct PortableTm - { - public uint Second; - public uint Minute; - public uint Hour; - public uint DayOfMonth; - public uint Month; - public uint Year; - public uint DayOfWeek; - public uint DayOfYear; - public uint IsDst; - - public uint GmtOff; - public uint Timezone; - } - - enum PCompression : uint - { - None = 0, - Gzip = 1, - Bzip2 = 2, - Lzo = 3 - } - - enum PEncryption : uint - { - None = 0 - } - - /// <summary> - /// Partimage CMainHeader - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct PartimageMainHeader - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] - public byte[] szFileSystem; // ext2fs, ntfs, reiserfs, ... - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DESCRIPTION)] - public byte[] szPartDescription; // user description of the partition - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEVICENAMELEN)] - public byte[] szOriginalDevice; // original partition name - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4095)] - public byte[] szFirstImageFilepath; //MAXPATHLEN]; // for splitted image files - - // system and hardware infos - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] - public byte[] szUnameSysname; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] - public byte[] szUnameNodename; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] - public byte[] szUnameRelease; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] - public byte[] szUnameVersion; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] - public byte[] szUnameMachine; - - public PCompression dwCompression; // COMPRESS_XXXXXX - public uint dwMainFlags; - public PortableTm dateCreate; // date of image creation - public ulong qwPartSize; // size of the partition in bytes - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_HOSTNAMESIZE)] - public byte[] szHostname; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] - public byte[] szVersion; // version of the image file - - // MBR backup - public uint dwMbrCount; // how many MBR are saved in the image file - public uint dwMbrSize; // size of a MBR record (allow to change the size in the next versions) - - // future encryption support - public PEncryption dwEncryptAlgo; // algo used to encrypt data - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] cHashTestKey; // used to test the password without giving it - - // reserved for future use (save DiskLabel, Extended partitions, ...) - public uint dwReservedFuture000; - public uint dwReservedFuture001; - public uint dwReservedFuture002; - public uint dwReservedFuture003; - public uint dwReservedFuture004; - public uint dwReservedFuture005; - public uint dwReservedFuture006; - public uint dwReservedFuture007; - public uint dwReservedFuture008; - public uint dwReservedFuture009; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6524)] - public byte[] cReserved; // Adjust to fit with total header size - - public uint crc; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CMbr // must be 1024 - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MBR_SIZE_WHOLE)] - public byte[] cData; - public uint dwDataCRC; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEVICENAMELEN)] - public byte[] szDevice; // ex: "hda" - - // disk identificators - ulong qwBlocksCount; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DESC_MODEL)] - public byte[] szDescModel; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 884)] - public byte[] cReserved; // for future use - - //public byte[] szDescGeometry[MAX_DESC_GEOMETRY]; - //public byte[] szDescIdentify[MAX_DESC_IDENTIFY]; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CCheck - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - byte[] cMagic; // must be 'C','H','K' - public uint dwCRC; // CRC of the CHECK_FREQUENCY blocks - public ulong qwPos; // number of the last block written - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CLocalHeader // size must be 16384 (adjust the reserved data) - { - public ulong qwBlockSize; - public ulong qwUsedBlocks; - public ulong qwBlocksCount; - public ulong qwBitmapSize; // bytes in the bitmap - public ulong qwBadBlocksCount; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] - public byte[] szLabel; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16280)] - public byte[] cReserved; // Adjust to fit with total header size - - public uint crc; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct CMainTail // size must be 16384 (adjust the reserved data) - { - public ulong qwCRC; - public uint dwVolumeNumber; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16372)] - public byte[] cReserved; // Adjust to fit with total header size - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Structs.cs b/DiscImageChef.DiscImages/Partimage/Structs.cs new file mode 100644 index 000000000..7eac0e6e6 --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Structs.cs @@ -0,0 +1,206 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage + { + /// <summary> + /// Partimage disk image header, little-endian + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct PartimageHeader + { + /// <summary> + /// Magic, <see cref="Partimage.partimageMagic" /> + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] magic; + /// <summary> + /// Source filesystem + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public byte[] version; + /// <summary> + /// Volume number + /// </summary> + public uint volumeNumber; + /// <summary> + /// Image identifier + /// </summary> + public ulong identificator; + /// <summary> + /// Empty space + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 404)] + public byte[] reserved; + } + + struct PortableTm + { + public uint Second; + public uint Minute; + public uint Hour; + public uint DayOfMonth; + public uint Month; + public uint Year; + public uint DayOfWeek; + public uint DayOfYear; + public uint IsDst; + + public uint GmtOff; + public uint Timezone; + } + + /// <summary> + /// Partimage CMainHeader + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct PartimageMainHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] + public byte[] szFileSystem; // ext2fs, ntfs, reiserfs, ... + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DESCRIPTION)] + public byte[] szPartDescription; // user description of the partition + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEVICENAMELEN)] + public byte[] szOriginalDevice; // original partition name + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4095)] + public byte[] szFirstImageFilepath; //MAXPATHLEN]; // for splitted image files + + // system and hardware infos + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] + public byte[] szUnameSysname; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] + public byte[] szUnameNodename; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] + public byte[] szUnameRelease; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] + public byte[] szUnameVersion; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_UNAMEINFOLEN)] + public byte[] szUnameMachine; + + public PCompression dwCompression; // COMPRESS_XXXXXX + public uint dwMainFlags; + public PortableTm dateCreate; // date of image creation + public ulong qwPartSize; // size of the partition in bytes + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_HOSTNAMESIZE)] + public byte[] szHostname; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public byte[] szVersion; // version of the image file + + // MBR backup + public uint dwMbrCount; // how many MBR are saved in the image file + public uint dwMbrSize; // size of a MBR record (allow to change the size in the next versions) + + // future encryption support + public PEncryption dwEncryptAlgo; // algo used to encrypt data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] cHashTestKey; // used to test the password without giving it + + // reserved for future use (save DiskLabel, Extended partitions, ...) + public uint dwReservedFuture000; + public uint dwReservedFuture001; + public uint dwReservedFuture002; + public uint dwReservedFuture003; + public uint dwReservedFuture004; + public uint dwReservedFuture005; + public uint dwReservedFuture006; + public uint dwReservedFuture007; + public uint dwReservedFuture008; + public uint dwReservedFuture009; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6524)] + public byte[] cReserved; // Adjust to fit with total header size + + public uint crc; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CMbr // must be 1024 + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MBR_SIZE_WHOLE)] + public byte[] cData; + public uint dwDataCRC; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEVICENAMELEN)] + public byte[] szDevice; // ex: "hda" + + // disk identificators + ulong qwBlocksCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DESC_MODEL)] + public byte[] szDescModel; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 884)] + public byte[] cReserved; // for future use + + //public byte[] szDescGeometry[MAX_DESC_GEOMETRY]; + //public byte[] szDescIdentify[MAX_DESC_IDENTIFY]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CCheck + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + byte[] cMagic; // must be 'C','H','K' + public uint dwCRC; // CRC of the CHECK_FREQUENCY blocks + public ulong qwPos; // number of the last block written + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CLocalHeader // size must be 16384 (adjust the reserved data) + { + public ulong qwBlockSize; + public ulong qwUsedBlocks; + public ulong qwBlocksCount; + public ulong qwBitmapSize; // bytes in the bitmap + public ulong qwBadBlocksCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public byte[] szLabel; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16280)] + public byte[] cReserved; // Adjust to fit with total header size + + public uint crc; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CMainTail // size must be 16384 (adjust the reserved data) + { + public ulong qwCRC; + public uint dwVolumeNumber; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16372)] + public byte[] cReserved; // Adjust to fit with total header size + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Partimage/Unsupported.cs b/DiscImageChef.DiscImages/Partimage/Unsupported.cs new file mode 100644 index 000000000..ff2f8a150 --- /dev/null +++ b/DiscImageChef.DiscImages/Partimage/Unsupported.cs @@ -0,0 +1,139 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by partimage disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Partimage + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + // TODO: All blocks contain a CRC32 that's incompatible with current implementation. Need to check for compatibility. + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW.cs b/DiscImageChef.DiscImages/QCOW.cs deleted file mode 100644 index 195eb3d59..000000000 --- a/DiscImageChef.DiscImages/QCOW.cs +++ /dev/null @@ -1,820 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : QCOW.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages QEMU Copy-On-Write disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; -using SharpCompress.Compressors; -using SharpCompress.Compressors.Deflate; - -namespace DiscImageChef.DiscImages -{ - public class Qcow : IWritableImage - { - /// <summary> - /// Magic number: 'Q', 'F', 'I', 0xFB - /// </summary> - const uint QCOW_MAGIC = 0x514649FB; - const uint QCOW_VERSION = 1; - const uint QCOW_ENCRYPTION_NONE = 0; - const uint QCOW_ENCRYPTION_AES = 1; - const ulong QCOW_COMPRESSED = 0x8000000000000000; - - const int MAX_CACHE_SIZE = 16777216; - - const int MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; - Dictionary<ulong, byte[]> clusterCache; - int clusterSectors; - int clusterSize; - ImageInfo imageInfo; - - Stream imageStream; - - ulong l1Mask; - int l1Shift; - uint l1Size; - ulong[] l1Table; - ulong l2Mask; - int l2Size; - Dictionary<ulong, ulong[]> l2TableCache; - int maxClusterCache; - int maxL2TableCache; - - QCowHeader qHdr; - - Dictionary<ulong, byte[]> sectorCache; - ulong sectorMask; - FileStream writingStream; - - public Qcow() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = "1", - Application = "QEMU", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "QEMU Copy-On-Write disk image"; - public Guid Id => new Guid("A5C35765-9FE2-469D-BBBF-ACDEBDB7B954"); - - public string Format => "QEMU Copy-On-Write"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] qHdrB = new byte[48]; - stream.Read(qHdrB, 0, 48); - qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCowHeader>(qHdrB); - - return qHdr.magic == QCOW_MAGIC && qHdr.version == QCOW_VERSION; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] qHdrB = new byte[48]; - stream.Read(qHdrB, 0, 48); - qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCowHeader>(qHdrB); - - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.version = {0}", qHdr.version); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_offset = {0}", qHdr.backing_file_offset); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_size = {0}", qHdr.backing_file_size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.mtime = {0}", qHdr.mtime); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.size = {0}", qHdr.size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.cluster_bits = {0}", qHdr.cluster_bits); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2_bits = {0}", qHdr.l2_bits); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.padding = {0}", qHdr.padding); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.crypt_method = {0}", qHdr.crypt_method); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1_table_offset = {0}", qHdr.l1_table_offset); - - if(qHdr.size <= 1) throw new ArgumentOutOfRangeException(nameof(qHdr.size), "Image size is too small"); - - if(qHdr.cluster_bits < 9 || qHdr.cluster_bits > 16) - throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_bits), - "Cluster size must be between 512 bytes and 64 Kbytes"); - - if(qHdr.l2_bits < 9 - 3 || qHdr.l2_bits > 16 - 3) - throw new ArgumentOutOfRangeException(nameof(qHdr.l2_bits), - "L2 size must be between 512 bytes and 64 Kbytes"); - - if(qHdr.crypt_method > QCOW_ENCRYPTION_AES) - throw new ArgumentOutOfRangeException(nameof(qHdr.crypt_method), "Invalid encryption method"); - - if(qHdr.crypt_method > QCOW_ENCRYPTION_NONE) - throw new NotImplementedException("AES encrypted images not yet supported"); - - if(qHdr.backing_file_offset != 0) - throw new NotImplementedException("Differencing images not yet supported"); - - int shift = qHdr.cluster_bits + qHdr.l2_bits; - - if(qHdr.size > ulong.MaxValue - (ulong)(1 << shift)) - throw new ArgumentOutOfRangeException(nameof(qHdr.size), "Image is too large"); - - clusterSize = 1 << qHdr.cluster_bits; - clusterSectors = 1 << (qHdr.cluster_bits - 9); - l1Size = (uint)((qHdr.size + (ulong)(1 << shift) - 1) >> shift); - l2Size = 1 << qHdr.l2_bits; - - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSize = {0}", clusterSize); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSectors = {0}", clusterSectors); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Size = {0}", l1Size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Size = {0}", l2Size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectors = {0}", imageInfo.Sectors); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - byte[] l1TableB = new byte[l1Size * 8]; - stream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); - stream.Read(l1TableB, 0, (int)l1Size * 8); - l1Table = new ulong[l1Size]; - DicConsole.DebugWriteLine("QCOW plugin", "Reading L1 table"); - for(long i = 0; i < l1Table.LongLength; i++) - l1Table[i] = BigEndianBitConverter.ToUInt64(l1TableB, (int)(i * 8)); - - l1Mask = 0; - int c = 0; - l1Shift = qHdr.l2_bits + qHdr.cluster_bits; - - for(int i = 0; i < 64; i++) - { - l1Mask <<= 1; - - if(c >= 64 - l1Shift) continue; - - l1Mask += 1; - c++; - } - - l2Mask = 0; - for(int i = 0; i < qHdr.l2_bits; i++) l2Mask = (l2Mask << 1) + 1; - - l2Mask <<= qHdr.cluster_bits; - - sectorMask = 0; - for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; - - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Mask = {0:X}", l1Mask); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Shift = {0}", l1Shift); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Mask = {0:X}", l2Mask); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectorMask = {0:X}", sectorMask); - - maxL2TableCache = MAX_CACHE_SIZE / (l2Size * 8); - maxClusterCache = MAX_CACHE_SIZE / clusterSize; - - imageStream = stream; - - sectorCache = new Dictionary<ulong, byte[]>(); - l2TableCache = new Dictionary<ulong, ulong[]>(); - clusterCache = new Dictionary<ulong, byte[]>(); - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = qHdr.mtime > 0 - ? DateHandlers.UnixUnsignedToDateTime(qHdr.mtime) - : imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = qHdr.size / 512; - imageInfo.SectorSize = 512; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.ImageSize = qHdr.size; - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - // Check cache - if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - - ulong byteAddress = sectorAddress * 512; - - ulong l1Off = (byteAddress & l1Mask) >> l1Shift; - - if((long)l1Off >= l1Table.LongLength) - throw new ArgumentOutOfRangeException(nameof(l1Off), - $"Trying to read past L1 table, position {l1Off} of a max {l1Table.LongLength}"); - - // TODO: Implement differential images - if(l1Table[l1Off] == 0) return new byte[512]; - - if(!l2TableCache.TryGetValue(l1Off, out ulong[] l2Table)) - { - l2Table = new ulong[l2Size]; - imageStream.Seek((long)l1Table[l1Off], SeekOrigin.Begin); - byte[] l2TableB = new byte[l2Size * 8]; - imageStream.Read(l2TableB, 0, l2Size * 8); - DicConsole.DebugWriteLine("QCOW plugin", "Reading L2 table #{0}", l1Off); - for(long i = 0; i < l2Table.LongLength; i++) - l2Table[i] = BigEndianBitConverter.ToUInt64(l2TableB, (int)(i * 8)); - - if(l2TableCache.Count >= maxL2TableCache) l2TableCache.Clear(); - - l2TableCache.Add(l1Off, l2Table); - } - - ulong l2Off = (byteAddress & l2Mask) >> qHdr.cluster_bits; - - ulong offset = l2Table[l2Off]; - - sector = new byte[512]; - - if(offset != 0) - { - if(!clusterCache.TryGetValue(offset, out byte[] cluster)) - { - if((offset & QCOW_COMPRESSED) == QCOW_COMPRESSED) - { - ulong compSizeMask = (ulong)(1 << qHdr.cluster_bits) - 1; - compSizeMask <<= 63 - qHdr.cluster_bits; - ulong offMask = ~compSizeMask ^ QCOW_COMPRESSED; - - ulong realOff = offset & offMask; - ulong compSize = (offset & compSizeMask) >> (63 - qHdr.cluster_bits); - - byte[] zCluster = new byte[compSize]; - imageStream.Seek((long)realOff, SeekOrigin.Begin); - imageStream.Read(zCluster, 0, (int)compSize); - - DeflateStream zStream = - new DeflateStream(new MemoryStream(zCluster), CompressionMode.Decompress); - cluster = new byte[clusterSize]; - int read = zStream.Read(cluster, 0, clusterSize); - - if(read != clusterSize) - throw new - IOException($"Unable to decompress cluster, expected {clusterSize} bytes got {read}"); - } - else - { - cluster = new byte[clusterSize]; - imageStream.Seek((long)offset, SeekOrigin.Begin); - imageStream.Read(cluster, 0, clusterSize); - } - - if(clusterCache.Count >= maxClusterCache) clusterCache.Clear(); - - clusterCache.Add(offset, cluster); - } - - Array.Copy(cluster, (int)(byteAddress & sectorMask), sector, 0, 512); - } - - if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - - return sector; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream ms = new MemoryStream(); - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".qcow", ".qc"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - // TODO: Correct this calculation - if(sectors * sectorSize / 65536 > uint.MaxValue) - { - ErrorMessage = "Too many sectors for selected cluster size"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - qHdr = new QCowHeader - { - magic = QCOW_MAGIC, - version = QCOW_VERSION, - size = sectors * sectorSize, - cluster_bits = 12, - l2_bits = 9, - l1_table_offset = (ulong)Marshal.SizeOf(typeof(QCowHeader)) - }; - - int shift = qHdr.cluster_bits + qHdr.l2_bits; - clusterSize = 1 << qHdr.cluster_bits; - clusterSectors = 1 << (qHdr.cluster_bits - 9); - l1Size = (uint)((qHdr.size + (ulong)(1 << shift) - 1) >> shift); - l2Size = 1 << qHdr.l2_bits; - - l1Table = new ulong[l1Size]; - - l1Mask = 0; - int c = 0; - l1Shift = qHdr.l2_bits + qHdr.cluster_bits; - - for(int i = 0; i < 64; i++) - { - l1Mask <<= 1; - - if(c >= 64 - l1Shift) continue; - - l1Mask += 1; - c++; - } - - l2Mask = 0; - for(int i = 0; i < qHdr.l2_bits; i++) l2Mask = (l2Mask << 1) + 1; - - l2Mask <<= qHdr.cluster_bits; - - sectorMask = 0; - for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; - - byte[] empty = new byte[qHdr.l1_table_offset + l1Size * 8]; - writingStream.Write(empty, 0, empty.Length); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - ulong byteAddress = sectorAddress * 512; - - ulong l1Off = (byteAddress & l1Mask) >> l1Shift; - - if((long)l1Off >= l1Table.LongLength) - throw new ArgumentOutOfRangeException(nameof(l1Off), - $"Trying to write past L1 table, position {l1Off} of a max {l1Table.LongLength}"); - - if(l1Table[l1Off] == 0) - { - writingStream.Seek(0, SeekOrigin.End); - l1Table[l1Off] = (ulong)writingStream.Position; - byte[] l2TableB = new byte[l2Size * 8]; - writingStream.Seek(0, SeekOrigin.End); - writingStream.Write(l2TableB, 0, l2TableB.Length); - } - - writingStream.Position = (long)l1Table[l1Off]; - - ulong l2Off = (byteAddress & l2Mask) >> qHdr.cluster_bits; - - writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - byte[] entry = new byte[8]; - writingStream.Read(entry, 0, 8); - ulong offset = BigEndianBitConverter.ToUInt64(entry, 0); - - if(offset == 0) - { - offset = (ulong)writingStream.Length; - byte[] cluster = new byte[clusterSize]; - entry = BigEndianBitConverter.GetBytes(offset); - writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); - writingStream.Write(entry, 0, 8); - writingStream.Seek(0, SeekOrigin.End); - writingStream.Write(cluster, 0, cluster.Length); - } - - writingStream.Seek((long)(offset + (byteAddress & sectorMask)), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - // TODO: This can be optimized - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - for(uint i = 0; i < length; i++) - { - byte[] tmp = new byte[imageInfo.SectorSize]; - Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); - if(!WriteSector(tmp, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - qHdr.mtime = (uint)(DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds; - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.magic), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.version), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_offset), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_size), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.mtime), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.size), 0, 8); - writingStream.WriteByte(qHdr.cluster_bits); - writingStream.WriteByte(qHdr.l2_bits); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.padding), 0, 2); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.crypt_method), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.l1_table_offset), 0, 8); - - writingStream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); - for(long i = 0; i < l1Table.LongLength; i++) - writingStream.Write(BigEndianBitConverter.GetBytes(l1Table[i]), 0, 8); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - /// <summary> - /// QCOW header, big-endian - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct QCowHeader - { - /// <summary> - /// <see cref="Qcow.QCOW_MAGIC" /> - /// </summary> - public uint magic; - /// <summary> - /// Must be 1 - /// </summary> - public uint version; - /// <summary> - /// Offset inside file to string containing backing file - /// </summary> - public ulong backing_file_offset; - /// <summary> - /// Size of <see cref="backing_file_offset" /> - /// </summary> - public uint backing_file_size; - /// <summary> - /// Modification time - /// </summary> - public uint mtime; - /// <summary> - /// Size in bytes - /// </summary> - public ulong size; - /// <summary> - /// Cluster bits - /// </summary> - public byte cluster_bits; - /// <summary> - /// L2 table bits - /// </summary> - public byte l2_bits; - /// <summary> - /// Padding - /// </summary> - public ushort padding; - /// <summary> - /// Encryption method - /// </summary> - public uint crypt_method; - /// <summary> - /// Offset to L1 table - /// </summary> - public ulong l1_table_offset; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/Constants.cs b/DiscImageChef.DiscImages/QCOW/Constants.cs new file mode 100644 index 000000000..2ce8e06f4 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/Constants.cs @@ -0,0 +1,49 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow + { + /// <summary> + /// Magic number: 'Q', 'F', 'I', 0xFB + /// </summary> + const uint QCOW_MAGIC = 0x514649FB; + const uint QCOW_VERSION = 1; + const uint QCOW_ENCRYPTION_NONE = 0; + const uint QCOW_ENCRYPTION_AES = 1; + const ulong QCOW_COMPRESSED = 0x8000000000000000; + + const int MAX_CACHE_SIZE = 16777216; + const int MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/Identify.cs b/DiscImageChef.DiscImages/QCOW/Identify.cs new file mode 100644 index 000000000..33db7bbac --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/Identify.cs @@ -0,0 +1,54 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] qHdrB = new byte[48]; + stream.Read(qHdrB, 0, 48); + qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCowHeader>(qHdrB); + + return qHdr.magic == QCOW_MAGIC && qHdr.version == QCOW_VERSION; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/Properties.cs b/DiscImageChef.DiscImages/QCOW/Properties.cs new file mode 100644 index 000000000..7f249f027 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/Properties.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow + { + public ImageInfo Info => imageInfo; + + public string Name => "QEMU Copy-On-Write disk image"; + public Guid Id => new Guid("A5C35765-9FE2-469D-BBBF-ACDEBDB7B954"); + + public string Format => "QEMU Copy-On-Write"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".qcow", ".qc"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/QCOW.cs b/DiscImageChef.DiscImages/QCOW/QCOW.cs new file mode 100644 index 000000000..4023b3079 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/QCOW.cs @@ -0,0 +1,89 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : QCOW.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow : IWritableImage + { + Dictionary<ulong, byte[]> clusterCache; + int clusterSectors; + int clusterSize; + ImageInfo imageInfo; + Stream imageStream; + ulong l1Mask; + int l1Shift; + uint l1Size; + ulong[] l1Table; + ulong l2Mask; + int l2Size; + Dictionary<ulong, ulong[]> l2TableCache; + int maxClusterCache; + int maxL2TableCache; + QCowHeader qHdr; + Dictionary<ulong, byte[]> sectorCache; + ulong sectorMask; + FileStream writingStream; + + public Qcow() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = "1", + Application = "QEMU", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/Read.cs b/DiscImageChef.DiscImages/QCOW/Read.cs new file mode 100644 index 000000000..fe991355a --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/Read.cs @@ -0,0 +1,278 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; +using SharpCompress.Compressors; +using SharpCompress.Compressors.Deflate; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] qHdrB = new byte[48]; + stream.Read(qHdrB, 0, 48); + qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCowHeader>(qHdrB); + + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.version = {0}", qHdr.version); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_offset = {0}", qHdr.backing_file_offset); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_size = {0}", qHdr.backing_file_size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.mtime = {0}", qHdr.mtime); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.size = {0}", qHdr.size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.cluster_bits = {0}", qHdr.cluster_bits); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2_bits = {0}", qHdr.l2_bits); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.padding = {0}", qHdr.padding); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.crypt_method = {0}", qHdr.crypt_method); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1_table_offset = {0}", qHdr.l1_table_offset); + + if(qHdr.size <= 1) throw new ArgumentOutOfRangeException(nameof(qHdr.size), "Image size is too small"); + + if(qHdr.cluster_bits < 9 || qHdr.cluster_bits > 16) + throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_bits), + "Cluster size must be between 512 bytes and 64 Kbytes"); + + if(qHdr.l2_bits < 9 - 3 || qHdr.l2_bits > 16 - 3) + throw new ArgumentOutOfRangeException(nameof(qHdr.l2_bits), + "L2 size must be between 512 bytes and 64 Kbytes"); + + if(qHdr.crypt_method > QCOW_ENCRYPTION_AES) + throw new ArgumentOutOfRangeException(nameof(qHdr.crypt_method), "Invalid encryption method"); + + if(qHdr.crypt_method > QCOW_ENCRYPTION_NONE) + throw new NotImplementedException("AES encrypted images not yet supported"); + + if(qHdr.backing_file_offset != 0) + throw new NotImplementedException("Differencing images not yet supported"); + + int shift = qHdr.cluster_bits + qHdr.l2_bits; + + if(qHdr.size > ulong.MaxValue - (ulong)(1 << shift)) + throw new ArgumentOutOfRangeException(nameof(qHdr.size), "Image is too large"); + + clusterSize = 1 << qHdr.cluster_bits; + clusterSectors = 1 << (qHdr.cluster_bits - 9); + l1Size = (uint)((qHdr.size + (ulong)(1 << shift) - 1) >> shift); + l2Size = 1 << qHdr.l2_bits; + + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSize = {0}", clusterSize); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSectors = {0}", clusterSectors); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Size = {0}", l1Size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Size = {0}", l2Size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectors = {0}", imageInfo.Sectors); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + byte[] l1TableB = new byte[l1Size * 8]; + stream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); + stream.Read(l1TableB, 0, (int)l1Size * 8); + l1Table = new ulong[l1Size]; + DicConsole.DebugWriteLine("QCOW plugin", "Reading L1 table"); + for(long i = 0; i < l1Table.LongLength; i++) + l1Table[i] = BigEndianBitConverter.ToUInt64(l1TableB, (int)(i * 8)); + + l1Mask = 0; + int c = 0; + l1Shift = qHdr.l2_bits + qHdr.cluster_bits; + + for(int i = 0; i < 64; i++) + { + l1Mask <<= 1; + + if(c >= 64 - l1Shift) continue; + + l1Mask += 1; + c++; + } + + l2Mask = 0; + for(int i = 0; i < qHdr.l2_bits; i++) l2Mask = (l2Mask << 1) + 1; + + l2Mask <<= qHdr.cluster_bits; + + sectorMask = 0; + for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; + + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Mask = {0:X}", l1Mask); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Shift = {0}", l1Shift); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Mask = {0:X}", l2Mask); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectorMask = {0:X}", sectorMask); + + maxL2TableCache = MAX_CACHE_SIZE / (l2Size * 8); + maxClusterCache = MAX_CACHE_SIZE / clusterSize; + + imageStream = stream; + + sectorCache = new Dictionary<ulong, byte[]>(); + l2TableCache = new Dictionary<ulong, ulong[]>(); + clusterCache = new Dictionary<ulong, byte[]>(); + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = qHdr.mtime > 0 + ? DateHandlers.UnixUnsignedToDateTime(qHdr.mtime) + : imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = qHdr.size / 512; + imageInfo.SectorSize = 512; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.ImageSize = qHdr.size; + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + // Check cache + if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; + + ulong byteAddress = sectorAddress * 512; + + ulong l1Off = (byteAddress & l1Mask) >> l1Shift; + + if((long)l1Off >= l1Table.LongLength) + throw new ArgumentOutOfRangeException(nameof(l1Off), + $"Trying to read past L1 table, position {l1Off} of a max {l1Table.LongLength}"); + + // TODO: Implement differential images + if(l1Table[l1Off] == 0) return new byte[512]; + + if(!l2TableCache.TryGetValue(l1Off, out ulong[] l2Table)) + { + l2Table = new ulong[l2Size]; + imageStream.Seek((long)l1Table[l1Off], SeekOrigin.Begin); + byte[] l2TableB = new byte[l2Size * 8]; + imageStream.Read(l2TableB, 0, l2Size * 8); + DicConsole.DebugWriteLine("QCOW plugin", "Reading L2 table #{0}", l1Off); + for(long i = 0; i < l2Table.LongLength; i++) + l2Table[i] = BigEndianBitConverter.ToUInt64(l2TableB, (int)(i * 8)); + + if(l2TableCache.Count >= maxL2TableCache) l2TableCache.Clear(); + + l2TableCache.Add(l1Off, l2Table); + } + + ulong l2Off = (byteAddress & l2Mask) >> qHdr.cluster_bits; + + ulong offset = l2Table[l2Off]; + + sector = new byte[512]; + + if(offset != 0) + { + if(!clusterCache.TryGetValue(offset, out byte[] cluster)) + { + if((offset & QCOW_COMPRESSED) == QCOW_COMPRESSED) + { + ulong compSizeMask = (ulong)(1 << qHdr.cluster_bits) - 1; + compSizeMask <<= 63 - qHdr.cluster_bits; + ulong offMask = ~compSizeMask ^ QCOW_COMPRESSED; + + ulong realOff = offset & offMask; + ulong compSize = (offset & compSizeMask) >> (63 - qHdr.cluster_bits); + + byte[] zCluster = new byte[compSize]; + imageStream.Seek((long)realOff, SeekOrigin.Begin); + imageStream.Read(zCluster, 0, (int)compSize); + + DeflateStream zStream = + new DeflateStream(new MemoryStream(zCluster), CompressionMode.Decompress); + cluster = new byte[clusterSize]; + int read = zStream.Read(cluster, 0, clusterSize); + + if(read != clusterSize) + throw new + IOException($"Unable to decompress cluster, expected {clusterSize} bytes got {read}"); + } + else + { + cluster = new byte[clusterSize]; + imageStream.Seek((long)offset, SeekOrigin.Begin); + imageStream.Read(cluster, 0, clusterSize); + } + + if(clusterCache.Count >= maxClusterCache) clusterCache.Clear(); + + clusterCache.Add(offset, cluster); + } + + Array.Copy(cluster, (int)(byteAddress & sectorMask), sector, 0, 512); + } + + if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/Structs.cs b/DiscImageChef.DiscImages/QCOW/Structs.cs new file mode 100644 index 000000000..c55484584 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/Structs.cs @@ -0,0 +1,91 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow + { + /// <summary> + /// QCOW header, big-endian + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct QCowHeader + { + /// <summary> + /// <see cref="Qcow.QCOW_MAGIC" /> + /// </summary> + public uint magic; + /// <summary> + /// Must be 1 + /// </summary> + public uint version; + /// <summary> + /// Offset inside file to string containing backing file + /// </summary> + public ulong backing_file_offset; + /// <summary> + /// Size of <see cref="backing_file_offset" /> + /// </summary> + public uint backing_file_size; + /// <summary> + /// Modification time + /// </summary> + public uint mtime; + /// <summary> + /// Size in bytes + /// </summary> + public ulong size; + /// <summary> + /// Cluster bits + /// </summary> + public byte cluster_bits; + /// <summary> + /// L2 table bits + /// </summary> + public byte l2_bits; + /// <summary> + /// Padding + /// </summary> + public ushort padding; + /// <summary> + /// Encryption method + /// </summary> + public uint crypt_method; + /// <summary> + /// Offset to L1 table + /// </summary> + public ulong l1_table_offset; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/Unsupported.cs b/DiscImageChef.DiscImages/QCOW/Unsupported.cs new file mode 100644 index 000000000..ad2216286 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW/Write.cs b/DiscImageChef.DiscImages/QCOW/Write.cs new file mode 100644 index 000000000..21380a86d --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW/Write.cs @@ -0,0 +1,326 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes QEMU Copy-On-Write disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + // TODO: Correct this calculation + if(sectors * sectorSize / 65536 > uint.MaxValue) + { + ErrorMessage = "Too many sectors for selected cluster size"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + qHdr = new QCowHeader + { + magic = QCOW_MAGIC, + version = QCOW_VERSION, + size = sectors * sectorSize, + cluster_bits = 12, + l2_bits = 9, + l1_table_offset = (ulong)Marshal.SizeOf(typeof(QCowHeader)) + }; + + int shift = qHdr.cluster_bits + qHdr.l2_bits; + clusterSize = 1 << qHdr.cluster_bits; + clusterSectors = 1 << (qHdr.cluster_bits - 9); + l1Size = (uint)((qHdr.size + (ulong)(1 << shift) - 1) >> shift); + l2Size = 1 << qHdr.l2_bits; + + l1Table = new ulong[l1Size]; + + l1Mask = 0; + int c = 0; + l1Shift = qHdr.l2_bits + qHdr.cluster_bits; + + for(int i = 0; i < 64; i++) + { + l1Mask <<= 1; + + if(c >= 64 - l1Shift) continue; + + l1Mask += 1; + c++; + } + + l2Mask = 0; + for(int i = 0; i < qHdr.l2_bits; i++) l2Mask = (l2Mask << 1) + 1; + + l2Mask <<= qHdr.cluster_bits; + + sectorMask = 0; + for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; + + byte[] empty = new byte[qHdr.l1_table_offset + l1Size * 8]; + writingStream.Write(empty, 0, empty.Length); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + ulong byteAddress = sectorAddress * 512; + + ulong l1Off = (byteAddress & l1Mask) >> l1Shift; + + if((long)l1Off >= l1Table.LongLength) + throw new ArgumentOutOfRangeException(nameof(l1Off), + $"Trying to write past L1 table, position {l1Off} of a max {l1Table.LongLength}"); + + if(l1Table[l1Off] == 0) + { + writingStream.Seek(0, SeekOrigin.End); + l1Table[l1Off] = (ulong)writingStream.Position; + byte[] l2TableB = new byte[l2Size * 8]; + writingStream.Seek(0, SeekOrigin.End); + writingStream.Write(l2TableB, 0, l2TableB.Length); + } + + writingStream.Position = (long)l1Table[l1Off]; + + ulong l2Off = (byteAddress & l2Mask) >> qHdr.cluster_bits; + + writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + byte[] entry = new byte[8]; + writingStream.Read(entry, 0, 8); + ulong offset = BigEndianBitConverter.ToUInt64(entry, 0); + + if(offset == 0) + { + offset = (ulong)writingStream.Length; + byte[] cluster = new byte[clusterSize]; + entry = BigEndianBitConverter.GetBytes(offset); + writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); + writingStream.Write(entry, 0, 8); + writingStream.Seek(0, SeekOrigin.End); + writingStream.Write(cluster, 0, cluster.Length); + } + + writingStream.Seek((long)(offset + (byteAddress & sectorMask)), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + // TODO: This can be optimized + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + for(uint i = 0; i < length; i++) + { + byte[] tmp = new byte[imageInfo.SectorSize]; + Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); + if(!WriteSector(tmp, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + qHdr.mtime = (uint)(DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds; + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.magic), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.version), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_offset), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_size), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.mtime), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.size), 0, 8); + writingStream.WriteByte(qHdr.cluster_bits); + writingStream.WriteByte(qHdr.l2_bits); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.padding), 0, 2); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.crypt_method), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.l1_table_offset), 0, 8); + + writingStream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); + for(long i = 0; i < l1Table.LongLength; i++) + writingStream.Write(BigEndianBitConverter.GetBytes(l1Table[i]), 0, 8); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2.cs b/DiscImageChef.DiscImages/QCOW2.cs deleted file mode 100644 index b0993c715..000000000 --- a/DiscImageChef.DiscImages/QCOW2.cs +++ /dev/null @@ -1,908 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : QCOW2.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages QEMU Copy-On-Write v2 disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; -using SharpCompress.Compressors; -using SharpCompress.Compressors.Deflate; - -namespace DiscImageChef.DiscImages -{ - public class Qcow2 : IWritableImage - { - /// <summary> - /// Magic number: 'Q', 'F', 'I', 0xFB - /// </summary> - const uint QCOW_MAGIC = 0x514649FB; - const uint QCOW_VERSION2 = 2; - const uint QCOW_VERSION3 = 3; - const uint QCOW_ENCRYPTION_NONE = 0; - const uint QCOW_ENCRYPTION_AES = 1; - - const ulong QCOW_FEATURE_DIRTY = 0x01; - const ulong QCOW_FEATURE_CORRUPT = 0x02; - const ulong QCOW_FEATURE_MASK = 0xFFFFFFFFFFFFFFFC; - - const ulong QCOW_COMPAT_FEATURE_LAZY_REFCOUNTS = 0x01; - const ulong QCOW_AUTO_CLEAR_FEATURE_BITMAP = 0x01; - - const ulong QCOW_FLAGS_MASK = 0x3FFFFFFFFFFFFFFF; - const ulong QCOW_COPIED = 0x8000000000000000; - const ulong QCOW_COMPRESSED = 0x4000000000000000; - - const ulong QCOW_HEADER_EXTENSION_BACKING_FILE = 0xE2792ACA; - const ulong QCOW_HEADER_EXTENSION_FEATURE_TABLE = 0x6803F857; - const ulong QCOW_HEADER_EXTENSION_BITMAPS = 0x23852875; - - const int MAX_CACHE_SIZE = 16777216; - - const int MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; - Dictionary<ulong, byte[]> clusterCache; - int clusterSectors; - int clusterSize; - ImageInfo imageInfo; - - Stream imageStream; - - ulong l1Mask; - int l1Shift; - ulong[] l1Table; - int l2Bits; - ulong l2Mask; - int l2Size; - Dictionary<ulong, ulong[]> l2TableCache; - int maxClusterCache; - int maxL2TableCache; - - QCow2Header qHdr; - ulong[] refCountTable; - - Dictionary<ulong, byte[]> sectorCache; - ulong sectorMask; - FileStream writingStream; - - public Qcow2() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = "QEMU", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "QEMU Copy-On-Write disk image v2"; - public Guid Id => new Guid("F20107CB-95B3-4398-894B-975261F1E8C5"); - - public string Format => "QEMU Copy-On-Write"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] qHdrB = new byte[Marshal.SizeOf(qHdr)]; - stream.Read(qHdrB, 0, Marshal.SizeOf(qHdr)); - qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCow2Header>(qHdrB); - - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.version = {0}", qHdr.version); - - return qHdr.magic == QCOW_MAGIC && (qHdr.version == QCOW_VERSION2 || qHdr.version == QCOW_VERSION3); - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] qHdrB = new byte[Marshal.SizeOf(qHdr)]; - stream.Read(qHdrB, 0, Marshal.SizeOf(qHdr)); - qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCow2Header>(qHdrB); - - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.version = {0}", qHdr.version); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_offset = {0}", qHdr.backing_file_offset); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_size = {0}", qHdr.backing_file_size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.cluster_bits = {0}", qHdr.cluster_bits); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.size = {0}", qHdr.size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.crypt_method = {0}", qHdr.crypt_method); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1_size = {0}", qHdr.l1_size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1_table_offset = {0}", qHdr.l1_table_offset); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.refcount_table_offset = {0}", qHdr.refcount_table_offset); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.refcount_table_clusters = {0}", - qHdr.refcount_table_clusters); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.nb_snapshots = {0}", qHdr.nb_snapshots); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.snapshots_offset = {0}", qHdr.snapshots_offset); - - if(qHdr.version >= QCOW_VERSION3) - { - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.features = {0:X}", qHdr.features); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.compat_features = {0:X}", qHdr.compat_features); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.autoclear_features = {0:X}", qHdr.autoclear_features); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.refcount_order = {0}", qHdr.refcount_order); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.header_length = {0}", qHdr.header_length); - - if((qHdr.features & QCOW_FEATURE_MASK) != 0) - throw new - ImageNotSupportedException($"Unknown incompatible features {qHdr.features & QCOW_FEATURE_MASK:X} enabled, not proceeding."); - } - - if(qHdr.size <= 1) throw new ArgumentOutOfRangeException(nameof(qHdr.size), "Image size is too small"); - - if(qHdr.cluster_bits < 9 || qHdr.cluster_bits > 16) - throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_bits), - "Cluster size must be between 512 bytes and 64 Kbytes"); - - if(qHdr.crypt_method > QCOW_ENCRYPTION_AES) - throw new ArgumentOutOfRangeException(nameof(qHdr.crypt_method), "Invalid encryption method"); - - if(qHdr.crypt_method > QCOW_ENCRYPTION_NONE) - throw new NotImplementedException("AES encrypted images not yet supported"); - - if(qHdr.backing_file_offset != 0) - throw new NotImplementedException("Differencing images not yet supported"); - - clusterSize = 1 << (int)qHdr.cluster_bits; - clusterSectors = 1 << ((int)qHdr.cluster_bits - 9); - l2Bits = (int)(qHdr.cluster_bits - 3); - l2Size = 1 << l2Bits; - - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSize = {0}", clusterSize); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSectors = {0}", clusterSectors); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.qHdr.l1_size = {0}", qHdr.l1_size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Size = {0}", l2Size); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectors = {0}", imageInfo.Sectors); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - byte[] l1TableB = new byte[qHdr.l1_size * 8]; - stream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); - stream.Read(l1TableB, 0, (int)qHdr.l1_size * 8); - l1Table = new ulong[qHdr.l1_size]; - // TODO: Optimize this - DicConsole.DebugWriteLine("QCOW plugin", "Reading L1 table"); - for(long i = 0; i < l1Table.LongLength; i++) - l1Table[i] = BigEndianBitConverter.ToUInt64(l1TableB, (int)(i * 8)); - - l1Mask = 0; - int c = 0; - l1Shift = (int)(l2Bits + qHdr.cluster_bits); - - for(int i = 0; i < 64; i++) - { - l1Mask <<= 1; - - if(c >= 64 - l1Shift) continue; - - l1Mask += 1; - c++; - } - - l2Mask = 0; - for(int i = 0; i < l2Bits; i++) l2Mask = (l2Mask << 1) + 1; - - l2Mask <<= (int)qHdr.cluster_bits; - - sectorMask = 0; - for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; - - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Mask = {0:X}", l1Mask); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Shift = {0}", l1Shift); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Mask = {0:X}", l2Mask); - DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectorMask = {0:X}", sectorMask); - - maxL2TableCache = MAX_CACHE_SIZE / (l2Size * 8); - maxClusterCache = MAX_CACHE_SIZE / clusterSize; - - imageStream = stream; - - sectorCache = new Dictionary<ulong, byte[]>(); - l2TableCache = new Dictionary<ulong, ulong[]>(); - clusterCache = new Dictionary<ulong, byte[]>(); - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = qHdr.size / 512; - imageInfo.SectorSize = 512; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.ImageSize = qHdr.size; - imageInfo.Version = $"{qHdr.version}"; - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - // Check cache - if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - - ulong byteAddress = sectorAddress * 512; - - ulong l1Off = (byteAddress & l1Mask) >> l1Shift; - - if((long)l1Off >= l1Table.LongLength) - throw new ArgumentOutOfRangeException(nameof(l1Off), - $"Trying to read past L1 table, position {l1Off} of a max {l1Table.LongLength}"); - - // TODO: Implement differential images - if(l1Table[l1Off] == 0) return new byte[512]; - - if(!l2TableCache.TryGetValue(l1Off, out ulong[] l2Table)) - { - l2Table = new ulong[l2Size]; - imageStream.Seek((long)(l1Table[l1Off] & QCOW_FLAGS_MASK), SeekOrigin.Begin); - byte[] l2TableB = new byte[l2Size * 8]; - imageStream.Read(l2TableB, 0, l2Size * 8); - DicConsole.DebugWriteLine("QCOW plugin", "Reading L2 table #{0}", l1Off); - for(long i = 0; i < l2Table.LongLength; i++) - l2Table[i] = BigEndianBitConverter.ToUInt64(l2TableB, (int)(i * 8)); - - if(l2TableCache.Count >= maxL2TableCache) l2TableCache.Clear(); - - l2TableCache.Add(l1Off, l2Table); - } - - ulong l2Off = (byteAddress & l2Mask) >> (int)qHdr.cluster_bits; - - ulong offset = l2Table[l2Off]; - - sector = new byte[512]; - - if((offset & QCOW_FLAGS_MASK) != 0) - { - if(!clusterCache.TryGetValue(offset, out byte[] cluster)) - { - if((offset & QCOW_COMPRESSED) == QCOW_COMPRESSED) - { - ulong compSizeMask = (ulong)(1 << (int)(qHdr.cluster_bits - 8)) - 1; - byte countbits = (byte)(qHdr.cluster_bits - 8); - compSizeMask <<= 62 - countbits; - ulong offMask = ~compSizeMask & QCOW_FLAGS_MASK; - - ulong realOff = offset & offMask; - ulong compSize = (((offset & compSizeMask) >> (62 - countbits)) + 1) * 512; - - byte[] zCluster = new byte[compSize]; - imageStream.Seek((long)realOff, SeekOrigin.Begin); - imageStream.Read(zCluster, 0, (int)compSize); - - DeflateStream zStream = - new DeflateStream(new MemoryStream(zCluster), CompressionMode.Decompress); - cluster = new byte[clusterSize]; - int read = zStream.Read(cluster, 0, clusterSize); - - if(read != clusterSize) - throw new - IOException($"Unable to decompress cluster, expected {clusterSize} bytes got {read}"); - } - else - { - cluster = new byte[clusterSize]; - imageStream.Seek((long)(offset & QCOW_FLAGS_MASK), SeekOrigin.Begin); - imageStream.Read(cluster, 0, clusterSize); - } - - if(clusterCache.Count >= maxClusterCache) clusterCache.Clear(); - - clusterCache.Add(offset, cluster); - } - - Array.Copy(cluster, (int)(byteAddress & sectorMask), sector, 0, 512); - } - - if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - - return sector; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream ms = new MemoryStream(); - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".qcow2", ".qc2", ".qcow3", ".qc3"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - // TODO: Correct this calculation - if(sectors * sectorSize / 65536 > uint.MaxValue) - { - ErrorMessage = "Too many sectors for selected cluster size"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - string extension = Path.GetExtension(path); - bool version3 = extension == ".qcow3" || extension == ".qc3"; - - qHdr = new QCow2Header - { - magic = QCOW_MAGIC, - version = version3 ? QCOW_VERSION3 : QCOW_VERSION2, - size = sectors * sectorSize, - cluster_bits = 16, - header_length = (uint)Marshal.SizeOf(typeof(QCow2Header)) - }; - - clusterSize = 1 << (int)qHdr.cluster_bits; - clusterSectors = 1 << ((int)qHdr.cluster_bits - 9); - l2Bits = (int)(qHdr.cluster_bits - 3); - l2Size = 1 << l2Bits; - - l1Mask = 0; - int c = 0; - l1Shift = (int)(l2Bits + qHdr.cluster_bits); - - for(int i = 0; i < 64; i++) - { - l1Mask <<= 1; - - if(c >= 64 - l1Shift) continue; - - l1Mask += 1; - c++; - } - - l2Mask = 0; - for(int i = 0; i < l2Bits; i++) l2Mask = (l2Mask << 1) + 1; - - l2Mask <<= (int)qHdr.cluster_bits; - - sectorMask = 0; - for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; - - qHdr.l1_size = (uint)(qHdr.size >> l1Shift); - if(qHdr.l1_size == 0) qHdr.l1_size = 1; - l1Table = new ulong[qHdr.l1_size]; - - ulong clusters = qHdr.size / (ulong)clusterSize; - ulong refCountBlocks = clusters * 2 / (ulong)clusterSize; - if(refCountBlocks == 0) refCountBlocks = 1; - - qHdr.refcount_table_offset = (ulong)clusterSize; - qHdr.refcount_table_clusters = (uint)(refCountBlocks * 8 / (ulong)clusterSize); - if(qHdr.refcount_table_clusters == 0) qHdr.refcount_table_clusters = 1; - refCountTable = new ulong[refCountBlocks]; - qHdr.l1_table_offset = qHdr.refcount_table_offset + (ulong)(qHdr.refcount_table_clusters * clusterSize); - ulong l1TableClusters = - qHdr.l1_size * 8 / (ulong)clusterSize; - if(l1TableClusters == 0) l1TableClusters = 1; - - byte[] empty = new byte[qHdr.l1_table_offset + l1TableClusters * (ulong)clusterSize]; - writingStream.Write(empty, 0, empty.Length); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - ulong byteAddress = sectorAddress * 512; - - ulong l1Off = (byteAddress & l1Mask) >> l1Shift; - - if((long)l1Off >= l1Table.LongLength) - throw new ArgumentOutOfRangeException(nameof(l1Off), - $"Trying to write past L1 table, position {l1Off} of a max {l1Table.LongLength}"); - - if(l1Table[l1Off] == 0) - { - writingStream.Seek(0, SeekOrigin.End); - l1Table[l1Off] = (ulong)writingStream.Position; - byte[] l2TableB = new byte[l2Size * 8]; - writingStream.Seek(0, SeekOrigin.End); - writingStream.Write(l2TableB, 0, l2TableB.Length); - } - - writingStream.Position = (long)l1Table[l1Off]; - - ulong l2Off = (byteAddress & l2Mask) >> (int)qHdr.cluster_bits; - - writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - byte[] entry = new byte[8]; - writingStream.Read(entry, 0, 8); - ulong offset = BigEndianBitConverter.ToUInt64(entry, 0); - - if(offset == 0) - { - offset = (ulong)writingStream.Length; - byte[] cluster = new byte[clusterSize]; - entry = BigEndianBitConverter.GetBytes(offset); - writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); - writingStream.Write(entry, 0, 8); - writingStream.Seek(0, SeekOrigin.End); - writingStream.Write(cluster, 0, cluster.Length); - } - - writingStream.Seek((long)(offset + (byteAddress & sectorMask)), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - int refCountBlockEntries = clusterSize * 8 / 16; - ulong refCountBlockIndex = offset / (ulong)clusterSize % (ulong)refCountBlockEntries; - ulong refCountTableIndex = offset / (ulong)clusterSize / (ulong)refCountBlockEntries; - - ulong refBlockOffset = refCountTable[refCountTableIndex]; - - if(refBlockOffset == 0) - { - refBlockOffset = (ulong)writingStream.Length; - refCountTable[refCountTableIndex] = refBlockOffset; - byte[] cluster = new byte[clusterSize]; - writingStream.Seek(0, SeekOrigin.End); - writingStream.Write(cluster, 0, cluster.Length); - } - - writingStream.Seek((long)(refBlockOffset + refCountBlockIndex), SeekOrigin.Begin); - writingStream.Write(new byte[] {0, 1}, 0, 2); - - ErrorMessage = ""; - return true; - } - - // TODO: This can be optimized - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - for(uint i = 0; i < length; i++) - { - byte[] tmp = new byte[imageInfo.SectorSize]; - Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); - if(!WriteSector(tmp, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.magic), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.version), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_offset), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_size), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.cluster_bits), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.size), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.crypt_method), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.l1_size), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.l1_table_offset), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.refcount_table_offset), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.refcount_table_clusters), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.nb_snapshots), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.snapshots_offset), 0, 8); - if(qHdr.version == QCOW_VERSION3) - { - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.features), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.compat_features), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.autoclear_features), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.refcount_order), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.header_length), 0, 4); - } - - writingStream.Seek((long)qHdr.refcount_table_offset, SeekOrigin.Begin); - for(long i = 0; i < refCountTable.LongLength; i++) - writingStream.Write(BigEndianBitConverter.GetBytes(refCountTable[i]), 0, 8); - - writingStream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); - for(long i = 0; i < l1Table.LongLength; i++) - writingStream.Write(BigEndianBitConverter.GetBytes(l1Table[i]), 0, 8); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - /// <summary> - /// QCOW header, big-endian - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct QCow2Header - { - /// <summary> - /// <see cref="Qcow2.QCOW_MAGIC" /> - /// </summary> - public uint magic; - /// <summary> - /// Must be 1 - /// </summary> - public uint version; - /// <summary> - /// Offset inside file to string containing backing file - /// </summary> - public ulong backing_file_offset; - /// <summary> - /// Size of <see cref="backing_file_offset" /> - /// </summary> - public uint backing_file_size; - /// <summary> - /// Cluster bits - /// </summary> - public uint cluster_bits; - /// <summary> - /// Size in bytes - /// </summary> - public ulong size; - /// <summary> - /// Encryption method - /// </summary> - public uint crypt_method; - /// <summary> - /// Size of L1 table - /// </summary> - public uint l1_size; - /// <summary> - /// Offset to L1 table - /// </summary> - public ulong l1_table_offset; - /// <summary> - /// Offset to reference count table - /// </summary> - public ulong refcount_table_offset; - /// <summary> - /// How many clusters does the refcount table span - /// </summary> - public uint refcount_table_clusters; - /// <summary> - /// Number of snapshots - /// </summary> - public uint nb_snapshots; - /// <summary> - /// Offset to QCowSnapshotHeader - /// </summary> - public ulong snapshots_offset; - - // Added in version 3 - public ulong features; - public ulong compat_features; - public ulong autoclear_features; - public uint refcount_order; - public uint header_length; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/Constants.cs b/DiscImageChef.DiscImages/QCOW2/Constants.cs new file mode 100644 index 000000000..08bd92204 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/Constants.cs @@ -0,0 +1,64 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 + { + /// <summary> + /// Magic number: 'Q', 'F', 'I', 0xFB + /// </summary> + const uint QCOW_MAGIC = 0x514649FB; + const uint QCOW_VERSION2 = 2; + const uint QCOW_VERSION3 = 3; + const uint QCOW_ENCRYPTION_NONE = 0; + const uint QCOW_ENCRYPTION_AES = 1; + + const ulong QCOW_FEATURE_DIRTY = 0x01; + const ulong QCOW_FEATURE_CORRUPT = 0x02; + const ulong QCOW_FEATURE_MASK = 0xFFFFFFFFFFFFFFFC; + + const ulong QCOW_COMPAT_FEATURE_LAZY_REFCOUNTS = 0x01; + const ulong QCOW_AUTO_CLEAR_FEATURE_BITMAP = 0x01; + + const ulong QCOW_FLAGS_MASK = 0x3FFFFFFFFFFFFFFF; + const ulong QCOW_COPIED = 0x8000000000000000; + const ulong QCOW_COMPRESSED = 0x4000000000000000; + + const ulong QCOW_HEADER_EXTENSION_BACKING_FILE = 0xE2792ACA; + const ulong QCOW_HEADER_EXTENSION_FEATURE_TABLE = 0x6803F857; + const ulong QCOW_HEADER_EXTENSION_BITMAPS = 0x23852875; + + const int MAX_CACHE_SIZE = 16777216; + const int MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/Identify.cs b/DiscImageChef.DiscImages/QCOW2/Identify.cs new file mode 100644 index 000000000..59ceab991 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/Identify.cs @@ -0,0 +1,59 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] qHdrB = new byte[Marshal.SizeOf(qHdr)]; + stream.Read(qHdrB, 0, Marshal.SizeOf(qHdr)); + qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCow2Header>(qHdrB); + + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.version = {0}", qHdr.version); + + return qHdr.magic == QCOW_MAGIC && (qHdr.version == QCOW_VERSION2 || qHdr.version == QCOW_VERSION3); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/Properties.cs b/DiscImageChef.DiscImages/QCOW2/Properties.cs new file mode 100644 index 000000000..116234090 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/Properties.cs @@ -0,0 +1,78 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 + { + public ImageInfo Info => imageInfo; + + public string Name => "QEMU Copy-On-Write disk image v2"; + public Guid Id => new Guid("F20107CB-95B3-4398-894B-975261F1E8C5"); + + public string Format => "QEMU Copy-On-Write"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".qcow2", ".qc2", ".qcow3", ".qc3"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/QCOW2.cs b/DiscImageChef.DiscImages/QCOW2/QCOW2.cs new file mode 100644 index 000000000..4ad702523 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/QCOW2.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : QCOW2.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 : IWritableImage + { + Dictionary<ulong, byte[]> clusterCache; + int clusterSectors; + int clusterSize; + ImageInfo imageInfo; + Stream imageStream; + ulong l1Mask; + int l1Shift; + ulong[] l1Table; + int l2Bits; + ulong l2Mask; + int l2Size; + Dictionary<ulong, ulong[]> l2TableCache; + int maxClusterCache; + int maxL2TableCache; + QCow2Header qHdr; + ulong[] refCountTable; + Dictionary<ulong, byte[]> sectorCache; + ulong sectorMask; + FileStream writingStream; + + public Qcow2() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = "QEMU", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/Read.cs b/DiscImageChef.DiscImages/QCOW2/Read.cs new file mode 100644 index 000000000..b69f5c3fb --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/Read.cs @@ -0,0 +1,288 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; +using SharpCompress.Compressors; +using SharpCompress.Compressors.Deflate; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] qHdrB = new byte[Marshal.SizeOf(qHdr)]; + stream.Read(qHdrB, 0, Marshal.SizeOf(qHdr)); + qHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<QCow2Header>(qHdrB); + + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.version = {0}", qHdr.version); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_offset = {0}", qHdr.backing_file_offset); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.backing_file_size = {0}", qHdr.backing_file_size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.cluster_bits = {0}", qHdr.cluster_bits); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.size = {0}", qHdr.size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.crypt_method = {0}", qHdr.crypt_method); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1_size = {0}", qHdr.l1_size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1_table_offset = {0}", qHdr.l1_table_offset); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.refcount_table_offset = {0}", qHdr.refcount_table_offset); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.refcount_table_clusters = {0}", + qHdr.refcount_table_clusters); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.nb_snapshots = {0}", qHdr.nb_snapshots); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.snapshots_offset = {0}", qHdr.snapshots_offset); + + if(qHdr.version >= QCOW_VERSION3) + { + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.features = {0:X}", qHdr.features); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.compat_features = {0:X}", qHdr.compat_features); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.autoclear_features = {0:X}", qHdr.autoclear_features); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.refcount_order = {0}", qHdr.refcount_order); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.header_length = {0}", qHdr.header_length); + + if((qHdr.features & QCOW_FEATURE_MASK) != 0) + throw new + ImageNotSupportedException($"Unknown incompatible features {qHdr.features & QCOW_FEATURE_MASK:X} enabled, not proceeding."); + } + + if(qHdr.size <= 1) throw new ArgumentOutOfRangeException(nameof(qHdr.size), "Image size is too small"); + + if(qHdr.cluster_bits < 9 || qHdr.cluster_bits > 16) + throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_bits), + "Cluster size must be between 512 bytes and 64 Kbytes"); + + if(qHdr.crypt_method > QCOW_ENCRYPTION_AES) + throw new ArgumentOutOfRangeException(nameof(qHdr.crypt_method), "Invalid encryption method"); + + if(qHdr.crypt_method > QCOW_ENCRYPTION_NONE) + throw new NotImplementedException("AES encrypted images not yet supported"); + + if(qHdr.backing_file_offset != 0) + throw new NotImplementedException("Differencing images not yet supported"); + + clusterSize = 1 << (int)qHdr.cluster_bits; + clusterSectors = 1 << ((int)qHdr.cluster_bits - 9); + l2Bits = (int)(qHdr.cluster_bits - 3); + l2Size = 1 << l2Bits; + + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSize = {0}", clusterSize); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.clusterSectors = {0}", clusterSectors); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.qHdr.l1_size = {0}", qHdr.l1_size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Size = {0}", l2Size); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectors = {0}", imageInfo.Sectors); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + byte[] l1TableB = new byte[qHdr.l1_size * 8]; + stream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); + stream.Read(l1TableB, 0, (int)qHdr.l1_size * 8); + l1Table = new ulong[qHdr.l1_size]; + // TODO: Optimize this + DicConsole.DebugWriteLine("QCOW plugin", "Reading L1 table"); + for(long i = 0; i < l1Table.LongLength; i++) + l1Table[i] = BigEndianBitConverter.ToUInt64(l1TableB, (int)(i * 8)); + + l1Mask = 0; + int c = 0; + l1Shift = (int)(l2Bits + qHdr.cluster_bits); + + for(int i = 0; i < 64; i++) + { + l1Mask <<= 1; + + if(c >= 64 - l1Shift) continue; + + l1Mask += 1; + c++; + } + + l2Mask = 0; + for(int i = 0; i < l2Bits; i++) l2Mask = (l2Mask << 1) + 1; + + l2Mask <<= (int)qHdr.cluster_bits; + + sectorMask = 0; + for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; + + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Mask = {0:X}", l1Mask); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l1Shift = {0}", l1Shift); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.l2Mask = {0:X}", l2Mask); + DicConsole.DebugWriteLine("QCOW plugin", "qHdr.sectorMask = {0:X}", sectorMask); + + maxL2TableCache = MAX_CACHE_SIZE / (l2Size * 8); + maxClusterCache = MAX_CACHE_SIZE / clusterSize; + + imageStream = stream; + + sectorCache = new Dictionary<ulong, byte[]>(); + l2TableCache = new Dictionary<ulong, ulong[]>(); + clusterCache = new Dictionary<ulong, byte[]>(); + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = qHdr.size / 512; + imageInfo.SectorSize = 512; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.ImageSize = qHdr.size; + imageInfo.Version = $"{qHdr.version}"; + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + // Check cache + if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; + + ulong byteAddress = sectorAddress * 512; + + ulong l1Off = (byteAddress & l1Mask) >> l1Shift; + + if((long)l1Off >= l1Table.LongLength) + throw new ArgumentOutOfRangeException(nameof(l1Off), + $"Trying to read past L1 table, position {l1Off} of a max {l1Table.LongLength}"); + + // TODO: Implement differential images + if(l1Table[l1Off] == 0) return new byte[512]; + + if(!l2TableCache.TryGetValue(l1Off, out ulong[] l2Table)) + { + l2Table = new ulong[l2Size]; + imageStream.Seek((long)(l1Table[l1Off] & QCOW_FLAGS_MASK), SeekOrigin.Begin); + byte[] l2TableB = new byte[l2Size * 8]; + imageStream.Read(l2TableB, 0, l2Size * 8); + DicConsole.DebugWriteLine("QCOW plugin", "Reading L2 table #{0}", l1Off); + for(long i = 0; i < l2Table.LongLength; i++) + l2Table[i] = BigEndianBitConverter.ToUInt64(l2TableB, (int)(i * 8)); + + if(l2TableCache.Count >= maxL2TableCache) l2TableCache.Clear(); + + l2TableCache.Add(l1Off, l2Table); + } + + ulong l2Off = (byteAddress & l2Mask) >> (int)qHdr.cluster_bits; + + ulong offset = l2Table[l2Off]; + + sector = new byte[512]; + + if((offset & QCOW_FLAGS_MASK) != 0) + { + if(!clusterCache.TryGetValue(offset, out byte[] cluster)) + { + if((offset & QCOW_COMPRESSED) == QCOW_COMPRESSED) + { + ulong compSizeMask = (ulong)(1 << (int)(qHdr.cluster_bits - 8)) - 1; + byte countbits = (byte)(qHdr.cluster_bits - 8); + compSizeMask <<= 62 - countbits; + ulong offMask = ~compSizeMask & QCOW_FLAGS_MASK; + + ulong realOff = offset & offMask; + ulong compSize = (((offset & compSizeMask) >> (62 - countbits)) + 1) * 512; + + byte[] zCluster = new byte[compSize]; + imageStream.Seek((long)realOff, SeekOrigin.Begin); + imageStream.Read(zCluster, 0, (int)compSize); + + DeflateStream zStream = + new DeflateStream(new MemoryStream(zCluster), CompressionMode.Decompress); + cluster = new byte[clusterSize]; + int read = zStream.Read(cluster, 0, clusterSize); + + if(read != clusterSize) + throw new + IOException($"Unable to decompress cluster, expected {clusterSize} bytes got {read}"); + } + else + { + cluster = new byte[clusterSize]; + imageStream.Seek((long)(offset & QCOW_FLAGS_MASK), SeekOrigin.Begin); + imageStream.Read(cluster, 0, clusterSize); + } + + if(clusterCache.Count >= maxClusterCache) clusterCache.Clear(); + + clusterCache.Add(offset, cluster); + } + + Array.Copy(cluster, (int)(byteAddress & sectorMask), sector, 0, 512); + } + + if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/Structs.cs b/DiscImageChef.DiscImages/QCOW2/Structs.cs new file mode 100644 index 000000000..9481a157f --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/Structs.cs @@ -0,0 +1,106 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 + { + /// <summary> + /// QCOW header, big-endian + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct QCow2Header + { + /// <summary> + /// <see cref="Qcow2.QCOW_MAGIC" /> + /// </summary> + public uint magic; + /// <summary> + /// Must be 1 + /// </summary> + public uint version; + /// <summary> + /// Offset inside file to string containing backing file + /// </summary> + public ulong backing_file_offset; + /// <summary> + /// Size of <see cref="backing_file_offset" /> + /// </summary> + public uint backing_file_size; + /// <summary> + /// Cluster bits + /// </summary> + public uint cluster_bits; + /// <summary> + /// Size in bytes + /// </summary> + public ulong size; + /// <summary> + /// Encryption method + /// </summary> + public uint crypt_method; + /// <summary> + /// Size of L1 table + /// </summary> + public uint l1_size; + /// <summary> + /// Offset to L1 table + /// </summary> + public ulong l1_table_offset; + /// <summary> + /// Offset to reference count table + /// </summary> + public ulong refcount_table_offset; + /// <summary> + /// How many clusters does the refcount table span + /// </summary> + public uint refcount_table_clusters; + /// <summary> + /// Number of snapshots + /// </summary> + public uint nb_snapshots; + /// <summary> + /// Offset to QCowSnapshotHeader + /// </summary> + public ulong snapshots_offset; + + // Added in version 3 + public ulong features; + public ulong compat_features; + public ulong autoclear_features; + public uint refcount_order; + public uint header_length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/Unsupported.cs b/DiscImageChef.DiscImages/QCOW2/Unsupported.cs new file mode 100644 index 000000000..6cf833285 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QCOW2/Write.cs b/DiscImageChef.DiscImages/QCOW2/Write.cs new file mode 100644 index 000000000..27a452629 --- /dev/null +++ b/DiscImageChef.DiscImages/QCOW2/Write.cs @@ -0,0 +1,372 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes QEMU Copy-On-Write v2 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Qcow2 + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + // TODO: Correct this calculation + if(sectors * sectorSize / 65536 > uint.MaxValue) + { + ErrorMessage = "Too many sectors for selected cluster size"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + string extension = Path.GetExtension(path); + bool version3 = extension == ".qcow3" || extension == ".qc3"; + + qHdr = new QCow2Header + { + magic = QCOW_MAGIC, + version = version3 ? QCOW_VERSION3 : QCOW_VERSION2, + size = sectors * sectorSize, + cluster_bits = 16, + header_length = (uint)Marshal.SizeOf(typeof(QCow2Header)) + }; + + clusterSize = 1 << (int)qHdr.cluster_bits; + clusterSectors = 1 << ((int)qHdr.cluster_bits - 9); + l2Bits = (int)(qHdr.cluster_bits - 3); + l2Size = 1 << l2Bits; + + l1Mask = 0; + int c = 0; + l1Shift = (int)(l2Bits + qHdr.cluster_bits); + + for(int i = 0; i < 64; i++) + { + l1Mask <<= 1; + + if(c >= 64 - l1Shift) continue; + + l1Mask += 1; + c++; + } + + l2Mask = 0; + for(int i = 0; i < l2Bits; i++) l2Mask = (l2Mask << 1) + 1; + + l2Mask <<= (int)qHdr.cluster_bits; + + sectorMask = 0; + for(int i = 0; i < qHdr.cluster_bits; i++) sectorMask = (sectorMask << 1) + 1; + + qHdr.l1_size = (uint)(qHdr.size >> l1Shift); + if(qHdr.l1_size == 0) qHdr.l1_size = 1; + l1Table = new ulong[qHdr.l1_size]; + + ulong clusters = qHdr.size / (ulong)clusterSize; + ulong refCountBlocks = clusters * 2 / (ulong)clusterSize; + if(refCountBlocks == 0) refCountBlocks = 1; + + qHdr.refcount_table_offset = (ulong)clusterSize; + qHdr.refcount_table_clusters = (uint)(refCountBlocks * 8 / (ulong)clusterSize); + if(qHdr.refcount_table_clusters == 0) qHdr.refcount_table_clusters = 1; + refCountTable = new ulong[refCountBlocks]; + qHdr.l1_table_offset = qHdr.refcount_table_offset + (ulong)(qHdr.refcount_table_clusters * clusterSize); + ulong l1TableClusters = + qHdr.l1_size * 8 / (ulong)clusterSize; + if(l1TableClusters == 0) l1TableClusters = 1; + + byte[] empty = new byte[qHdr.l1_table_offset + l1TableClusters * (ulong)clusterSize]; + writingStream.Write(empty, 0, empty.Length); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + ulong byteAddress = sectorAddress * 512; + + ulong l1Off = (byteAddress & l1Mask) >> l1Shift; + + if((long)l1Off >= l1Table.LongLength) + throw new ArgumentOutOfRangeException(nameof(l1Off), + $"Trying to write past L1 table, position {l1Off} of a max {l1Table.LongLength}"); + + if(l1Table[l1Off] == 0) + { + writingStream.Seek(0, SeekOrigin.End); + l1Table[l1Off] = (ulong)writingStream.Position; + byte[] l2TableB = new byte[l2Size * 8]; + writingStream.Seek(0, SeekOrigin.End); + writingStream.Write(l2TableB, 0, l2TableB.Length); + } + + writingStream.Position = (long)l1Table[l1Off]; + + ulong l2Off = (byteAddress & l2Mask) >> (int)qHdr.cluster_bits; + + writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + byte[] entry = new byte[8]; + writingStream.Read(entry, 0, 8); + ulong offset = BigEndianBitConverter.ToUInt64(entry, 0); + + if(offset == 0) + { + offset = (ulong)writingStream.Length; + byte[] cluster = new byte[clusterSize]; + entry = BigEndianBitConverter.GetBytes(offset); + writingStream.Seek((long)(l1Table[l1Off] + l2Off * 8), SeekOrigin.Begin); + writingStream.Write(entry, 0, 8); + writingStream.Seek(0, SeekOrigin.End); + writingStream.Write(cluster, 0, cluster.Length); + } + + writingStream.Seek((long)(offset + (byteAddress & sectorMask)), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + int refCountBlockEntries = clusterSize * 8 / 16; + ulong refCountBlockIndex = offset / (ulong)clusterSize % (ulong)refCountBlockEntries; + ulong refCountTableIndex = offset / (ulong)clusterSize / (ulong)refCountBlockEntries; + + ulong refBlockOffset = refCountTable[refCountTableIndex]; + + if(refBlockOffset == 0) + { + refBlockOffset = (ulong)writingStream.Length; + refCountTable[refCountTableIndex] = refBlockOffset; + byte[] cluster = new byte[clusterSize]; + writingStream.Seek(0, SeekOrigin.End); + writingStream.Write(cluster, 0, cluster.Length); + } + + writingStream.Seek((long)(refBlockOffset + refCountBlockIndex), SeekOrigin.Begin); + writingStream.Write(new byte[] {0, 1}, 0, 2); + + ErrorMessage = ""; + return true; + } + + // TODO: This can be optimized + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + for(uint i = 0; i < length; i++) + { + byte[] tmp = new byte[imageInfo.SectorSize]; + Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); + if(!WriteSector(tmp, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.magic), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.version), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_offset), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.backing_file_size), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.cluster_bits), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.size), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.crypt_method), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.l1_size), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.l1_table_offset), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.refcount_table_offset), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.refcount_table_clusters), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.nb_snapshots), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.snapshots_offset), 0, 8); + if(qHdr.version == QCOW_VERSION3) + { + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.features), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.compat_features), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.autoclear_features), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.refcount_order), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(qHdr.header_length), 0, 4); + } + + writingStream.Seek((long)qHdr.refcount_table_offset, SeekOrigin.Begin); + for(long i = 0; i < refCountTable.LongLength; i++) + writingStream.Write(BigEndianBitConverter.GetBytes(refCountTable[i]), 0, 8); + + writingStream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); + for(long i = 0; i < l1Table.LongLength; i++) + writingStream.Write(BigEndianBitConverter.GetBytes(l1Table[i]), 0, 8); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Constants.cs b/DiscImageChef.DiscImages/QED/Constants.cs new file mode 100644 index 000000000..cdf1d6a81 --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Constants.cs @@ -0,0 +1,66 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + /// <summary> + /// Magic number: 'Q', 'E', 'D', 0x00 + /// </summary> + const uint QED_MAGIC = 0x00444551; + + /// <summary> + /// Mask of unsupported incompatible features + /// </summary> + const ulong QED_FEATURE_MASK = 0xFFFFFFFFFFFFFFF8; + + /// <summary> + /// File is differential (has a backing file) + /// </summary> + const ulong QED_FEATURE_BACKING_FILE = 0x01; + /// <summary> + /// Image needs a consistency check before writing + /// </summary> + const ulong QED_FEATURE_NEEDS_CHECK = 0x02; + /// <summary> + /// Backing file is a raw disk image + /// </summary> + const ulong QED_FEATURE_RAW_BACKING = 0x04; + + const int MAX_CACHE_SIZE = 16777216; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; + const uint DEFAULT_CLUSTER_SIZE = 65536; + const uint DEFAULT_TABLE_SIZE = 4; + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Enums.cs b/DiscImageChef.DiscImages/QED/Enums.cs new file mode 100644 index 000000000..a30c24cfb --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Enums.cs @@ -0,0 +1,39 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Helpers.cs b/DiscImageChef.DiscImages/QED/Helpers.cs new file mode 100644 index 000000000..fd247fc4f --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Helpers.cs @@ -0,0 +1,38 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Identify.cs b/DiscImageChef.DiscImages/QED/Identify.cs new file mode 100644 index 000000000..cc6f72c32 --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Identify.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] qHdrB = new byte[64]; + stream.Read(qHdrB, 0, 64); + qHdr = new QedHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(64); + Marshal.Copy(qHdrB, 0, headerPtr, 64); + qHdr = (QedHeader)Marshal.PtrToStructure(headerPtr, typeof(QedHeader)); + Marshal.FreeHGlobal(headerPtr); + + return qHdr.magic == QED_MAGIC; + } + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Properties.cs b/DiscImageChef.DiscImages/QED/Properties.cs new file mode 100644 index 000000000..b798dd08b --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Properties.cs @@ -0,0 +1,60 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + public ImageInfo Info => imageInfo; + + public string Name => "QEMU Enhanced Disk image"; + public Guid Id => new Guid("B9DBB155-A69A-4C10-BF91-96BF431B9BB6"); + + public string Format => "QEMU Enhanced Disk"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED.cs b/DiscImageChef.DiscImages/QED/QED.cs similarity index 62% rename from DiscImageChef.DiscImages/QED.cs rename to DiscImageChef.DiscImages/QED/QED.cs index 886808053..2d3f93910 100644 --- a/DiscImageChef.DiscImages/QED.cs +++ b/DiscImageChef.DiscImages/QED/QED.cs @@ -45,53 +45,21 @@ using Schemas; namespace DiscImageChef.DiscImages { - public class Qed : IWritableImage + public partial class Qed : IWritableImage { - /// <summary> - /// Magic number: 'Q', 'E', 'D', 0x00 - /// </summary> - const uint QED_MAGIC = 0x00444551; - - /// <summary> - /// Mask of unsupported incompatible features - /// </summary> - const ulong QED_FEATURE_MASK = 0xFFFFFFFFFFFFFFF8; - - /// <summary> - /// File is differential (has a backing file) - /// </summary> - const ulong QED_FEATURE_BACKING_FILE = 0x01; - /// <summary> - /// Image needs a consistency check before writing - /// </summary> - const ulong QED_FEATURE_NEEDS_CHECK = 0x02; - /// <summary> - /// Backing file is a raw disk image - /// </summary> - const ulong QED_FEATURE_RAW_BACKING = 0x04; - - const int MAX_CACHE_SIZE = 16777216; - - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; - const uint DEFAULT_CLUSTER_SIZE = 65536; - const uint DEFAULT_TABLE_SIZE = 4; int clusterBits; Dictionary<ulong, byte[]> clusterCache; uint clusterSectors; ImageInfo imageInfo; - Stream imageStream; - - ulong l1Mask; + ulong l1Mask; int l1Shift; ulong[] l1Table; ulong l2Mask; Dictionary<ulong, ulong[]> l2TableCache; uint maxClusterCache; uint maxL2TableCache; - QedHeader qHdr; - Dictionary<ulong, byte[]> sectorCache; ulong sectorMask; uint tableSize; @@ -124,236 +92,8 @@ namespace DiscImageChef.DiscImages }; } - public ImageInfo Info => imageInfo; - public string Name => "QEMU Enhanced Disk image"; - public Guid Id => new Guid("B9DBB155-A69A-4C10-BF91-96BF431B9BB6"); - public string Format => "QEMU Enhanced Disk"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] qHdrB = new byte[64]; - stream.Read(qHdrB, 0, 64); - qHdr = new QedHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(64); - Marshal.Copy(qHdrB, 0, headerPtr, 64); - qHdr = (QedHeader)Marshal.PtrToStructure(headerPtr, typeof(QedHeader)); - Marshal.FreeHGlobal(headerPtr); - - return qHdr.magic == QED_MAGIC; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] qHdrB = new byte[64]; - stream.Read(qHdrB, 0, 64); - qHdr = new QedHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(64); - Marshal.Copy(qHdrB, 0, headerPtr, 64); - qHdr = (QedHeader)Marshal.PtrToStructure(headerPtr, typeof(QedHeader)); - Marshal.FreeHGlobal(headerPtr); - - DicConsole.DebugWriteLine("QED plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); - DicConsole.DebugWriteLine("QED plugin", "qHdr.cluster_size = {0}", qHdr.cluster_size); - DicConsole.DebugWriteLine("QED plugin", "qHdr.table_size = {0}", qHdr.table_size); - DicConsole.DebugWriteLine("QED plugin", "qHdr.header_size = {0}", qHdr.header_size); - DicConsole.DebugWriteLine("QED plugin", "qHdr.features = {0}", qHdr.features); - DicConsole.DebugWriteLine("QED plugin", "qHdr.compat_features = {0}", qHdr.compat_features); - DicConsole.DebugWriteLine("QED plugin", "qHdr.autoclear_features = {0}", qHdr.autoclear_features); - DicConsole.DebugWriteLine("QED plugin", "qHdr.l1_table_offset = {0}", qHdr.l1_table_offset); - DicConsole.DebugWriteLine("QED plugin", "qHdr.image_size = {0}", qHdr.image_size); - DicConsole.DebugWriteLine("QED plugin", "qHdr.backing_file_offset = {0}", qHdr.backing_file_offset); - DicConsole.DebugWriteLine("QED plugin", "qHdr.backing_file_size = {0}", qHdr.backing_file_size); - - if(qHdr.image_size <= 1) - throw new ArgumentOutOfRangeException(nameof(qHdr.image_size), "Image size is too small"); - - if(!IsPowerOfTwo(qHdr.cluster_size)) - throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_size), "Cluster size must be a power of 2"); - - if(qHdr.cluster_size < 4096 || qHdr.cluster_size > 67108864) - throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_size), - "Cluster size must be between 4 Kbytes and 64 Mbytes"); - - if(!IsPowerOfTwo(qHdr.table_size)) - throw new ArgumentOutOfRangeException(nameof(qHdr.table_size), "Table size must be a power of 2"); - - if(qHdr.table_size < 1 || qHdr.table_size > 16) - throw new ArgumentOutOfRangeException(nameof(qHdr.table_size), - "Table size must be between 1 and 16 clusters"); - - if((qHdr.features & QED_FEATURE_MASK) > 0) - throw new ArgumentOutOfRangeException(nameof(qHdr.features), - $"Image uses unknown incompatible features {qHdr.features & QED_FEATURE_MASK:X}"); - - if((qHdr.features & QED_FEATURE_BACKING_FILE) == QED_FEATURE_BACKING_FILE) - throw new NotImplementedException("Differencing images not yet supported"); - - clusterSectors = qHdr.cluster_size / 512; - tableSize = qHdr.cluster_size * qHdr.table_size / 8; - - DicConsole.DebugWriteLine("QED plugin", "qHdr.clusterSectors = {0}", clusterSectors); - DicConsole.DebugWriteLine("QED plugin", "qHdr.tableSize = {0}", tableSize); - - byte[] l1TableB = new byte[tableSize * 8]; - stream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); - stream.Read(l1TableB, 0, (int)tableSize * 8); - l1Table = new ulong[tableSize]; - DicConsole.DebugWriteLine("QED plugin", "Reading L1 table"); - for(long i = 0; i < l1Table.LongLength; i++) l1Table[i] = BitConverter.ToUInt64(l1TableB, (int)(i * 8)); - - l1Mask = 0; - int c = 0; - clusterBits = Ctz32(qHdr.cluster_size); - l2Mask = (tableSize - 1) << clusterBits; - l1Shift = clusterBits + Ctz32(tableSize); - - for(int i = 0; i < 64; i++) - { - l1Mask <<= 1; - - if(c >= 64 - l1Shift) continue; - - l1Mask += 1; - c++; - } - - sectorMask = 0; - for(int i = 0; i < clusterBits; i++) sectorMask = (sectorMask << 1) + 1; - - DicConsole.DebugWriteLine("QED plugin", "qHdr.clusterBits = {0}", clusterBits); - DicConsole.DebugWriteLine("QED plugin", "qHdr.l1Mask = {0:X}", l1Mask); - DicConsole.DebugWriteLine("QED plugin", "qHdr.l1Shift = {0}", l1Shift); - DicConsole.DebugWriteLine("QED plugin", "qHdr.l2Mask = {0:X}", l2Mask); - DicConsole.DebugWriteLine("QED plugin", "qHdr.sectorMask = {0:X}", sectorMask); - - maxL2TableCache = MAX_CACHE_SIZE / tableSize; - maxClusterCache = MAX_CACHE_SIZE / qHdr.cluster_size; - - imageStream = stream; - - sectorCache = new Dictionary<ulong, byte[]>(); - l2TableCache = new Dictionary<ulong, ulong[]>(); - clusterCache = new Dictionary<ulong, byte[]>(); - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = qHdr.image_size / 512; - imageInfo.SectorSize = 512; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.ImageSize = qHdr.image_size; - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - // Check cache - if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - - ulong byteAddress = sectorAddress * 512; - - ulong l1Off = (byteAddress & l1Mask) >> l1Shift; - - if((long)l1Off >= l1Table.LongLength) - throw new ArgumentOutOfRangeException(nameof(l1Off), - $"Trying to read past L1 table, position {l1Off} of a max {l1Table.LongLength}"); - - // TODO: Implement differential images - if(l1Table[l1Off] == 0) return new byte[512]; - - if(!l2TableCache.TryGetValue(l1Off, out ulong[] l2Table)) - { - l2Table = new ulong[tableSize]; - imageStream.Seek((long)l1Table[l1Off], SeekOrigin.Begin); - byte[] l2TableB = new byte[tableSize * 8]; - imageStream.Read(l2TableB, 0, (int)tableSize * 8); - DicConsole.DebugWriteLine("QED plugin", "Reading L2 table #{0}", l1Off); - for(long i = 0; i < l2Table.LongLength; i++) l2Table[i] = BitConverter.ToUInt64(l2TableB, (int)(i * 8)); - - if(l2TableCache.Count >= maxL2TableCache) l2TableCache.Clear(); - - l2TableCache.Add(l1Off, l2Table); - } - - ulong l2Off = (byteAddress & l2Mask) >> clusterBits; - - ulong offset = l2Table[l2Off]; - - sector = new byte[512]; - - if(offset != 0 && offset != 1) - { - if(!clusterCache.TryGetValue(offset, out byte[] cluster)) - { - cluster = new byte[qHdr.cluster_size]; - imageStream.Seek((long)offset, SeekOrigin.Begin); - imageStream.Read(cluster, 0, (int)qHdr.cluster_size); - - if(clusterCache.Count >= maxClusterCache) clusterCache.Clear(); - - clusterCache.Add(offset, cluster); - } - - Array.Copy(cluster, (int)(byteAddress & sectorMask), sector, 0, 512); - } - - if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - - return sector; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream ms = new MemoryStream(); - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) { diff --git a/DiscImageChef.DiscImages/QED/Read.cs b/DiscImageChef.DiscImages/QED/Read.cs new file mode 100644 index 000000000..1ce85ac82 --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Read.cs @@ -0,0 +1,244 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] qHdrB = new byte[64]; + stream.Read(qHdrB, 0, 64); + qHdr = new QedHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(64); + Marshal.Copy(qHdrB, 0, headerPtr, 64); + qHdr = (QedHeader)Marshal.PtrToStructure(headerPtr, typeof(QedHeader)); + Marshal.FreeHGlobal(headerPtr); + + DicConsole.DebugWriteLine("QED plugin", "qHdr.magic = 0x{0:X8}", qHdr.magic); + DicConsole.DebugWriteLine("QED plugin", "qHdr.cluster_size = {0}", qHdr.cluster_size); + DicConsole.DebugWriteLine("QED plugin", "qHdr.table_size = {0}", qHdr.table_size); + DicConsole.DebugWriteLine("QED plugin", "qHdr.header_size = {0}", qHdr.header_size); + DicConsole.DebugWriteLine("QED plugin", "qHdr.features = {0}", qHdr.features); + DicConsole.DebugWriteLine("QED plugin", "qHdr.compat_features = {0}", qHdr.compat_features); + DicConsole.DebugWriteLine("QED plugin", "qHdr.autoclear_features = {0}", qHdr.autoclear_features); + DicConsole.DebugWriteLine("QED plugin", "qHdr.l1_table_offset = {0}", qHdr.l1_table_offset); + DicConsole.DebugWriteLine("QED plugin", "qHdr.image_size = {0}", qHdr.image_size); + DicConsole.DebugWriteLine("QED plugin", "qHdr.backing_file_offset = {0}", qHdr.backing_file_offset); + DicConsole.DebugWriteLine("QED plugin", "qHdr.backing_file_size = {0}", qHdr.backing_file_size); + + if(qHdr.image_size <= 1) + throw new ArgumentOutOfRangeException(nameof(qHdr.image_size), "Image size is too small"); + + if(!IsPowerOfTwo(qHdr.cluster_size)) + throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_size), "Cluster size must be a power of 2"); + + if(qHdr.cluster_size < 4096 || qHdr.cluster_size > 67108864) + throw new ArgumentOutOfRangeException(nameof(qHdr.cluster_size), + "Cluster size must be between 4 Kbytes and 64 Mbytes"); + + if(!IsPowerOfTwo(qHdr.table_size)) + throw new ArgumentOutOfRangeException(nameof(qHdr.table_size), "Table size must be a power of 2"); + + if(qHdr.table_size < 1 || qHdr.table_size > 16) + throw new ArgumentOutOfRangeException(nameof(qHdr.table_size), + "Table size must be between 1 and 16 clusters"); + + if((qHdr.features & QED_FEATURE_MASK) > 0) + throw new ArgumentOutOfRangeException(nameof(qHdr.features), + $"Image uses unknown incompatible features {qHdr.features & QED_FEATURE_MASK:X}"); + + if((qHdr.features & QED_FEATURE_BACKING_FILE) == QED_FEATURE_BACKING_FILE) + throw new NotImplementedException("Differencing images not yet supported"); + + clusterSectors = qHdr.cluster_size / 512; + tableSize = qHdr.cluster_size * qHdr.table_size / 8; + + DicConsole.DebugWriteLine("QED plugin", "qHdr.clusterSectors = {0}", clusterSectors); + DicConsole.DebugWriteLine("QED plugin", "qHdr.tableSize = {0}", tableSize); + + byte[] l1TableB = new byte[tableSize * 8]; + stream.Seek((long)qHdr.l1_table_offset, SeekOrigin.Begin); + stream.Read(l1TableB, 0, (int)tableSize * 8); + l1Table = new ulong[tableSize]; + DicConsole.DebugWriteLine("QED plugin", "Reading L1 table"); + for(long i = 0; i < l1Table.LongLength; i++) l1Table[i] = BitConverter.ToUInt64(l1TableB, (int)(i * 8)); + + l1Mask = 0; + int c = 0; + clusterBits = Ctz32(qHdr.cluster_size); + l2Mask = (tableSize - 1) << clusterBits; + l1Shift = clusterBits + Ctz32(tableSize); + + for(int i = 0; i < 64; i++) + { + l1Mask <<= 1; + + if(c >= 64 - l1Shift) continue; + + l1Mask += 1; + c++; + } + + sectorMask = 0; + for(int i = 0; i < clusterBits; i++) sectorMask = (sectorMask << 1) + 1; + + DicConsole.DebugWriteLine("QED plugin", "qHdr.clusterBits = {0}", clusterBits); + DicConsole.DebugWriteLine("QED plugin", "qHdr.l1Mask = {0:X}", l1Mask); + DicConsole.DebugWriteLine("QED plugin", "qHdr.l1Shift = {0}", l1Shift); + DicConsole.DebugWriteLine("QED plugin", "qHdr.l2Mask = {0:X}", l2Mask); + DicConsole.DebugWriteLine("QED plugin", "qHdr.sectorMask = {0:X}", sectorMask); + + maxL2TableCache = MAX_CACHE_SIZE / tableSize; + maxClusterCache = MAX_CACHE_SIZE / qHdr.cluster_size; + + imageStream = stream; + + sectorCache = new Dictionary<ulong, byte[]>(); + l2TableCache = new Dictionary<ulong, ulong[]>(); + clusterCache = new Dictionary<ulong, byte[]>(); + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = qHdr.image_size / 512; + imageInfo.SectorSize = 512; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.ImageSize = qHdr.image_size; + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + // Check cache + if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; + + ulong byteAddress = sectorAddress * 512; + + ulong l1Off = (byteAddress & l1Mask) >> l1Shift; + + if((long)l1Off >= l1Table.LongLength) + throw new ArgumentOutOfRangeException(nameof(l1Off), + $"Trying to read past L1 table, position {l1Off} of a max {l1Table.LongLength}"); + + // TODO: Implement differential images + if(l1Table[l1Off] == 0) return new byte[512]; + + if(!l2TableCache.TryGetValue(l1Off, out ulong[] l2Table)) + { + l2Table = new ulong[tableSize]; + imageStream.Seek((long)l1Table[l1Off], SeekOrigin.Begin); + byte[] l2TableB = new byte[tableSize * 8]; + imageStream.Read(l2TableB, 0, (int)tableSize * 8); + DicConsole.DebugWriteLine("QED plugin", "Reading L2 table #{0}", l1Off); + for(long i = 0; i < l2Table.LongLength; i++) l2Table[i] = BitConverter.ToUInt64(l2TableB, (int)(i * 8)); + + if(l2TableCache.Count >= maxL2TableCache) l2TableCache.Clear(); + + l2TableCache.Add(l1Off, l2Table); + } + + ulong l2Off = (byteAddress & l2Mask) >> clusterBits; + + ulong offset = l2Table[l2Off]; + + sector = new byte[512]; + + if(offset != 0 && offset != 1) + { + if(!clusterCache.TryGetValue(offset, out byte[] cluster)) + { + cluster = new byte[qHdr.cluster_size]; + imageStream.Seek((long)offset, SeekOrigin.Begin); + imageStream.Read(cluster, 0, (int)qHdr.cluster_size); + + if(clusterCache.Count >= maxClusterCache) clusterCache.Clear(); + + clusterCache.Add(offset, cluster); + } + + Array.Copy(cluster, (int)(byteAddress & sectorMask), sector, 0, 512); + } + + if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Structs.cs b/DiscImageChef.DiscImages/QED/Structs.cs new file mode 100644 index 000000000..4a7ec20ce --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Structs.cs @@ -0,0 +1,38 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Unsupported.cs b/DiscImageChef.DiscImages/QED/Unsupported.cs new file mode 100644 index 000000000..ac38b3a06 --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Unsupported.cs @@ -0,0 +1,38 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/QED/Write.cs b/DiscImageChef.DiscImages/QED/Write.cs new file mode 100644 index 000000000..2cdc69d64 --- /dev/null +++ b/DiscImageChef.DiscImages/QED/Write.cs @@ -0,0 +1,38 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ +namespace DiscImageChef.DiscImages +{ + public partial class Qed + { + + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM.cs b/DiscImageChef.DiscImages/RayDIM.cs deleted file mode 100644 index ec2d0ad56..000000000 --- a/DiscImageChef.DiscImages/RayDIM.cs +++ /dev/null @@ -1,561 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : RayDIM.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Ray Arachelian's disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class RayDim : IWritableImage - { - const string REGEX_SIGNATURE = - @"Disk IMage VER (?<major>\d).(?<minor>\d) Copyright \(C\) (?<year>\d{4}) Ray Arachelian, All Rights Reserved\."; - - MemoryStream disk; - ImageInfo imageInfo; - FileStream writingStream; - - public RayDim() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Application = "Ray Arachelian's Disk IMage", - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "Ray Arachelian's Disk IMage"; - public Guid Id => new Guid("F541F4E7-C1E3-4A2D-B07F-D863E87AB961"); - public ImageInfo Info => imageInfo; - - public string Format => "Ray Arachelian's Disk IMage"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if(stream.Length < Marshal.SizeOf(typeof(RayHdr))) return false; - - byte[] buffer = new byte[Marshal.SizeOf(typeof(RayHdr))]; - stream.Seek(0, SeekOrigin.Begin); - stream.Read(buffer, 0, buffer.Length); - - IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); - Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); - RayHdr header = (RayHdr)Marshal.PtrToStructure(ftrPtr, typeof(RayHdr)); - Marshal.FreeHGlobal(ftrPtr); - - string signature = StringHandlers.CToString(header.signature); - - DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.signature = {0}", signature); - DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.diskType = {0}", header.diskType); - DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.heads = {0}", header.heads); - DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.cylinders = {0}", header.cylinders); - DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.sectorsPerTrack = {0}", - header.sectorsPerTrack); - - Regex sx = new Regex(REGEX_SIGNATURE); - Match sm = sx.Match(signature); - - DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.signature matches? = {0}", - sm.Success); - - return sm.Success; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if(stream.Length < Marshal.SizeOf(typeof(RayHdr))) return false; - - byte[] buffer = new byte[Marshal.SizeOf(typeof(RayHdr))]; - stream.Seek(0, SeekOrigin.Begin); - stream.Read(buffer, 0, buffer.Length); - - IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); - Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); - RayHdr header = (RayHdr)Marshal.PtrToStructure(ftrPtr, typeof(RayHdr)); - Marshal.FreeHGlobal(ftrPtr); - - string signature = StringHandlers.CToString(header.signature); - - Regex sx = new Regex(REGEX_SIGNATURE); - Match sm = sx.Match(signature); - - if(!sm.Success) return false; - - imageInfo.ApplicationVersion = $"{sm.Groups["major"].Value}.{sm.Groups["minor"].Value}"; - - imageInfo.Cylinders = (uint)(header.cylinders + 1); - imageInfo.Heads = (uint)(header.heads + 1); - imageInfo.SectorsPerTrack = header.sectorsPerTrack; - imageInfo.Sectors = imageInfo.Cylinders * imageInfo.Heads * imageInfo.SectorsPerTrack; - imageInfo.SectorSize = 512; - - byte[] sectors = new byte[imageInfo.SectorsPerTrack * imageInfo.SectorSize]; - disk = new MemoryStream(); - - for(int i = 0; i < imageInfo.SectorsPerTrack * imageInfo.SectorSize; i++) - { - stream.Read(sectors, 0, sectors.Length); - stream.Seek(imageInfo.SectorsPerTrack, SeekOrigin.Current); - disk.Write(sectors, 0, sectors.Length); - } - - imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, - (ushort)imageInfo.SectorsPerTrack, 512, MediaEncoding.MFM, - false)); - - switch(imageInfo.MediaType) - { - case MediaType.NEC_525_HD - when header.diskType == RayDiskTypes.Mf2hd || header.diskType == RayDiskTypes.Mf2ed: - imageInfo.MediaType = MediaType.NEC_35_HD_8; - break; - case MediaType.DOS_525_HD - when header.diskType == RayDiskTypes.Mf2hd || header.diskType == RayDiskTypes.Mf2ed: - imageInfo.MediaType = MediaType.NEC_35_HD_15; - break; - case MediaType.RX50 when header.diskType == RayDiskTypes.Md2dd || header.diskType == RayDiskTypes.Md2hd: - imageInfo.MediaType = MediaType.ATARI_35_SS_DD; - break; - } - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - - return true; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - disk.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - disk.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - - return buffer; - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - // TODO: Test with real hardware to see real supported media - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, - MediaType.ATARI_35_SS_DD_11, MediaType.DMF, MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, - MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, - MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, - MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, MediaType.FDFORMAT_35_DD, - MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, MediaType.FDFORMAT_525_HD, MediaType.RX50, - MediaType.XDF_35, MediaType.XDF_525 - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".dim"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors > 255 * 255 * 255) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool - variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); - imageInfo = new ImageInfo - { - MediaType = mediaType, - SectorSize = sectorSize, - Sectors = sectors, - Cylinders = geometry.cylinders, - Heads = geometry.heads, - SectorsPerTrack = geometry.sectorsPerTrack - }; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(RayHdr)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(RayHdr)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is set by media type - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - string headerSignature = - $"Disk IMage VER 1.0 Copyright (C) {DateTime.Now.Year:D4} Ray Arachelian, All Rights Reserved. DIC "; - RayHdr header = new RayHdr - { - signature = Encoding.ASCII.GetBytes(headerSignature), - cylinders = (byte)imageInfo.Cylinders, - diskType = RayDiskTypes.Mf2ed, - heads = (byte)imageInfo.Heads, - sectorsPerTrack = (byte)imageInfo.SectorsPerTrack - }; - header.signature[0x4A] = 0x00; - - byte[] hdr = new byte[Marshal.SizeOf(header)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.StructureToPtr(header, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct RayHdr - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] - public byte[] signature; - public RayDiskTypes diskType; - public byte cylinders; - public byte sectorsPerTrack; - public byte heads; - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - enum RayDiskTypes : byte - { - Md2dd = 1, - Md2hd = 2, - Mf2dd = 3, - Mf2hd = 4, - Mf2ed = 5 - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Constants.cs b/DiscImageChef.DiscImages/RayDIM/Constants.cs new file mode 100644 index 000000000..52a12e3a7 --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Constants.cs @@ -0,0 +1,40 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + const string REGEX_SIGNATURE = + @"Disk IMage VER (?<major>\d).(?<minor>\d) Copyright \(C\) (?<year>\d{4}) Ray Arachelian, All Rights Reserved\."; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Enums.cs b/DiscImageChef.DiscImages/RayDIM/Enums.cs new file mode 100644 index 000000000..96d9a9a76 --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Enums.cs @@ -0,0 +1,49 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics.CodeAnalysis; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum RayDiskTypes : byte + { + Md2dd = 1, + Md2hd = 2, + Mf2dd = 3, + Mf2hd = 4, + Mf2ed = 5 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Identify.cs b/DiscImageChef.DiscImages/RayDIM/Identify.cs new file mode 100644 index 000000000..3496bdbaf --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Identify.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if(stream.Length < Marshal.SizeOf(typeof(RayHdr))) return false; + + byte[] buffer = new byte[Marshal.SizeOf(typeof(RayHdr))]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(buffer, 0, buffer.Length); + + IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); + RayHdr header = (RayHdr)Marshal.PtrToStructure(ftrPtr, typeof(RayHdr)); + Marshal.FreeHGlobal(ftrPtr); + + string signature = StringHandlers.CToString(header.signature); + + DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.signature = {0}", signature); + DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.diskType = {0}", header.diskType); + DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.heads = {0}", header.heads); + DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.cylinders = {0}", header.cylinders); + DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.sectorsPerTrack = {0}", + header.sectorsPerTrack); + + Regex sx = new Regex(REGEX_SIGNATURE); + Match sm = sx.Match(signature); + + DicConsole.DebugWriteLine("Ray Arachelian's Disk IMage plugin", "header.signature matches? = {0}", + sm.Success); + + return sm.Success; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Properties.cs b/DiscImageChef.DiscImages/RayDIM/Properties.cs new file mode 100644 index 000000000..bb65e7e8a --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Properties.cs @@ -0,0 +1,82 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + public string Name => "Ray Arachelian's Disk IMage"; + public Guid Id => new Guid("F541F4E7-C1E3-4A2D-B07F-D863E87AB961"); + public ImageInfo Info => imageInfo; + + public string Format => "Ray Arachelian's Disk IMage"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + // TODO: Test with real hardware to see real supported media + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, + MediaType.ATARI_35_SS_DD_11, MediaType.DMF, MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, + MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, + MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, + MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, MediaType.FDFORMAT_35_DD, + MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, MediaType.FDFORMAT_525_HD, MediaType.RX50, + MediaType.XDF_35, MediaType.XDF_525 + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".dim"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/RayDIM.cs b/DiscImageChef.DiscImages/RayDIM/RayDIM.cs new file mode 100644 index 000000000..959f95295 --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/RayDIM.cs @@ -0,0 +1,72 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : RayDIM.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim : IWritableImage + { + MemoryStream disk; + ImageInfo imageInfo; + FileStream writingStream; + + public RayDim() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Application = "Ray Arachelian's Disk IMage", + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Read.cs b/DiscImageChef.DiscImages/RayDIM/Read.cs new file mode 100644 index 000000000..d0e6f1b14 --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Read.cs @@ -0,0 +1,130 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if(stream.Length < Marshal.SizeOf(typeof(RayHdr))) return false; + + byte[] buffer = new byte[Marshal.SizeOf(typeof(RayHdr))]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(buffer, 0, buffer.Length); + + IntPtr ftrPtr = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, ftrPtr, buffer.Length); + RayHdr header = (RayHdr)Marshal.PtrToStructure(ftrPtr, typeof(RayHdr)); + Marshal.FreeHGlobal(ftrPtr); + + string signature = StringHandlers.CToString(header.signature); + + Regex sx = new Regex(REGEX_SIGNATURE); + Match sm = sx.Match(signature); + + if(!sm.Success) return false; + + imageInfo.ApplicationVersion = $"{sm.Groups["major"].Value}.{sm.Groups["minor"].Value}"; + + imageInfo.Cylinders = (uint)(header.cylinders + 1); + imageInfo.Heads = (uint)(header.heads + 1); + imageInfo.SectorsPerTrack = header.sectorsPerTrack; + imageInfo.Sectors = imageInfo.Cylinders * imageInfo.Heads * imageInfo.SectorsPerTrack; + imageInfo.SectorSize = 512; + + byte[] sectors = new byte[imageInfo.SectorsPerTrack * imageInfo.SectorSize]; + disk = new MemoryStream(); + + for(int i = 0; i < imageInfo.SectorsPerTrack * imageInfo.SectorSize; i++) + { + stream.Read(sectors, 0, sectors.Length); + stream.Seek(imageInfo.SectorsPerTrack, SeekOrigin.Current); + disk.Write(sectors, 0, sectors.Length); + } + + imageInfo.MediaType = Geometry.GetMediaType(((ushort)imageInfo.Cylinders, (byte)imageInfo.Heads, + (ushort)imageInfo.SectorsPerTrack, 512, MediaEncoding.MFM, + false)); + + switch(imageInfo.MediaType) + { + case MediaType.NEC_525_HD + when header.diskType == RayDiskTypes.Mf2hd || header.diskType == RayDiskTypes.Mf2ed: + imageInfo.MediaType = MediaType.NEC_35_HD_8; + break; + case MediaType.DOS_525_HD + when header.diskType == RayDiskTypes.Mf2hd || header.diskType == RayDiskTypes.Mf2ed: + imageInfo.MediaType = MediaType.NEC_35_HD_15; + break; + case MediaType.RX50 when header.diskType == RayDiskTypes.Md2dd || header.diskType == RayDiskTypes.Md2hd: + imageInfo.MediaType = MediaType.ATARI_35_SS_DD; + break; + } + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + disk.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + disk.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Structs.cs b/DiscImageChef.DiscImages/RayDIM/Structs.cs new file mode 100644 index 000000000..2ee274729 --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Structs.cs @@ -0,0 +1,50 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct RayHdr + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] + public byte[] signature; + public RayDiskTypes diskType; + public byte cylinders; + public byte sectorsPerTrack; + public byte heads; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Unsupported.cs b/DiscImageChef.DiscImages/RayDIM/Unsupported.cs new file mode 100644 index 000000000..68b510089 --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Unsupported.cs @@ -0,0 +1,144 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifyMediaImage() + { + return null; + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RayDIM/Write.cs b/DiscImageChef.DiscImages/RayDIM/Write.cs new file mode 100644 index 000000000..c9b44f5d1 --- /dev/null +++ b/DiscImageChef.DiscImages/RayDIM/Write.cs @@ -0,0 +1,245 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Ray Arachelian's disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class RayDim + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors > 255 * 255 * 255) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool + variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); + imageInfo = new ImageInfo + { + MediaType = mediaType, + SectorSize = sectorSize, + Sectors = sectors, + Cylinders = geometry.cylinders, + Heads = geometry.heads, + SectorsPerTrack = geometry.sectorsPerTrack + }; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(RayHdr)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)((ulong)Marshal.SizeOf(typeof(RayHdr)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is set by media type + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + string headerSignature = + $"Disk IMage VER 1.0 Copyright (C) {DateTime.Now.Year:D4} Ray Arachelian, All Rights Reserved. DIC "; + RayHdr header = new RayHdr + { + signature = Encoding.ASCII.GetBytes(headerSignature), + cylinders = (byte)imageInfo.Cylinders, + diskType = RayDiskTypes.Mf2ed, + heads = (byte)imageInfo.Heads, + sectorsPerTrack = (byte)imageInfo.SectorsPerTrack + }; + header.signature[0x4A] = 0x00; + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.StructureToPtr(header, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde.cs b/DiscImageChef.DiscImages/RsIde.cs deleted file mode 100644 index a4491c1e4..000000000 --- a/DiscImageChef.DiscImages/RsIde.cs +++ /dev/null @@ -1,655 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : RsIde.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages RS-IDE disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using Schemas; -using static DiscImageChef.Decoders.ATA.Identify; -using Version = DiscImageChef.CommonTypes.Interop.Version; - -namespace DiscImageChef.DiscImages -{ - public class RsIde : IWritableImage - { - readonly byte[] signature = {0x52, 0x53, 0x2D, 0x49, 0x44, 0x45, 0x1A}; - ushort dataOff; - byte[] identify; - ImageInfo imageInfo; - - IFilter rsIdeImageFilter; - FileStream writingStream; - - public RsIde() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "RS-IDE Hard Disk Image"; - public Guid Id => new Guid("47C3E78D-2BE2-4BA5-AA6B-FEE27C86FC65"); - public ImageInfo Info => imageInfo; - - public string Format => "RS-IDE disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - byte[] magic = new byte[7]; - stream.Read(magic, 0, magic.Length); - - return magic.SequenceEqual(signature); - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - byte[] hdrB = new byte[Marshal.SizeOf(typeof(RsIdeHeader))]; - stream.Read(hdrB, 0, hdrB.Length); - - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(RsIdeHeader))); - Marshal.Copy(hdrB, 0, hdrPtr, Marshal.SizeOf(typeof(RsIdeHeader))); - RsIdeHeader hdr = (RsIdeHeader)Marshal.PtrToStructure(hdrPtr, typeof(RsIdeHeader)); - Marshal.FreeHGlobal(hdrPtr); - - if(!hdr.magic.SequenceEqual(signature)) return false; - - dataOff = hdr.dataOff; - - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.SectorSize = (uint)(hdr.flags.HasFlag(RsIdeFlags.HalfSectors) ? 256 : 512); - imageInfo.ImageSize = (ulong)(stream.Length - dataOff); - imageInfo.Sectors = imageInfo.ImageSize / imageInfo.SectorSize; - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.Version = $"{hdr.revision >> 8}.{hdr.revision & 0x0F}"; - - if(!ArrayHelpers.ArrayIsNullOrEmpty(hdr.identify)) - { - identify = new byte[512]; - Array.Copy(hdr.identify, 0, identify, 0, hdr.identify.Length); - IdentifyDevice? ataId = Decode(identify); - - if(ataId.HasValue) - { - imageInfo.ReadableMediaTags.Add(MediaTagType.ATA_IDENTIFY); - imageInfo.Cylinders = ataId.Value.Cylinders; - imageInfo.Heads = ataId.Value.Heads; - imageInfo.SectorsPerTrack = ataId.Value.SectorsPerCard; - imageInfo.DriveFirmwareRevision = ataId.Value.FirmwareRevision; - imageInfo.DriveModel = ataId.Value.Model; - imageInfo.DriveSerialNumber = ataId.Value.SerialNumber; - imageInfo.MediaSerialNumber = ataId.Value.MediaSerial; - imageInfo.MediaManufacturer = ataId.Value.MediaManufacturer; - } - } - - if(imageInfo.Cylinders == 0 || imageInfo.Heads == 0 || imageInfo.SectorsPerTrack == 0) - { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - } - - rsIdeImageFilter = imageFilter; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Stream stream = rsIdeImageFilter.GetDataForkStream(); - - stream.Seek((long)(dataOff + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - - return buffer; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - if(!imageInfo.ReadableMediaTags.Contains(tag) || tag != MediaTagType.ATA_IDENTIFY) - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - byte[] buffer = new byte[512]; - Array.Copy(identify, 0, buffer, 0, 512); - return buffer; - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.ATA_IDENTIFY}; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".ide"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 256 && sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors > 63 * 16 * 1024) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - if(tag != MediaTagType.ATA_IDENTIFY) - { - ErrorMessage = $"Unsupported media tag {tag}."; - return false; - } - - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - identify = new byte[106]; - Array.Copy(data, 0, identify, 0, 106); - return true; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream - .Seek((long)((ulong)Marshal.SizeOf(typeof(RsIdeHeader)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream - .Seek((long)((ulong)Marshal.SizeOf(typeof(RsIdeHeader)) + sectorAddress * imageInfo.SectorSize), - SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(imageInfo.Cylinders == 0) - { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - while(imageInfo.Cylinders == 0) - { - imageInfo.Heads--; - - if(imageInfo.Heads == 0) - { - imageInfo.SectorsPerTrack--; - imageInfo.Heads = 16; - } - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); - - if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; - } - } - - RsIdeHeader header = new RsIdeHeader - { - magic = signature, - identify = new byte[106], - dataOff = (ushort)Marshal.SizeOf(typeof(RsIdeHeader)), - revision = 1, - reserved = new byte[11] - }; - if(imageInfo.SectorSize == 256) header.flags = RsIdeFlags.HalfSectors; - - if(identify == null) - { - IdentifyDevice ataId = new IdentifyDevice - { - GeneralConfiguration = - GeneralConfigurationBit.UltraFastIDE | GeneralConfigurationBit.Fixed | - GeneralConfigurationBit.NotMFM | GeneralConfigurationBit.SoftSector, - Cylinders = (ushort)imageInfo.Cylinders, - Heads = (ushort)imageInfo.Heads, - SectorsPerTrack = (ushort)imageInfo.SectorsPerTrack, - VendorWord47 = 0x80, - Capabilities = - CapabilitiesBit.DMASupport | CapabilitiesBit.IORDY | CapabilitiesBit.LBASupport, - ExtendedIdentify = ExtendedIdentifyBit.Words54to58Valid, - CurrentCylinders = (ushort)imageInfo.Cylinders, - CurrentHeads = (ushort)imageInfo.Heads, - CurrentSectorsPerTrack = (ushort)imageInfo.SectorsPerTrack, - CurrentSectors = (uint)imageInfo.Sectors, - LBASectors = (uint)imageInfo.Sectors, - DMASupported = TransferMode.Mode0, - DMAActive = TransferMode.Mode0 - }; - - if(string.IsNullOrEmpty(imageInfo.DriveManufacturer)) imageInfo.DriveManufacturer = "DiscImageChef"; - - if(string.IsNullOrEmpty(imageInfo.DriveModel)) imageInfo.DriveModel = ""; - - if(string.IsNullOrEmpty(imageInfo.DriveFirmwareRevision)) Version.GetVersion(); - - if(string.IsNullOrEmpty(imageInfo.DriveSerialNumber)) - imageInfo.DriveSerialNumber = $"{new Random().NextDouble():16X}"; - - byte[] ataIdBytes = new byte[Marshal.SizeOf(ataId)]; - IntPtr ptr = Marshal.AllocHGlobal(512); - Marshal.StructureToPtr(ataId, ptr, true); - Marshal.Copy(ptr, ataIdBytes, 0, Marshal.SizeOf(ataId)); - Marshal.FreeHGlobal(ptr); - - Array.Copy(ScrambleAtaString(imageInfo.DriveManufacturer + " " + imageInfo.DriveModel, 40), 0, - ataIdBytes, 27 * 2, 40); - Array.Copy(ScrambleAtaString(imageInfo.DriveFirmwareRevision, 8), 0, ataIdBytes, 23 * 2, 8); - Array.Copy(ScrambleAtaString(imageInfo.DriveSerialNumber, 20), 0, ataIdBytes, 10 * 2, 20); - Array.Copy(ataIdBytes, 0, header.identify, 0, 106); - } - else Array.Copy(identify, 0, header.identify, 0, 106); - - byte[] hdr = new byte[Marshal.SizeOf(header)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.StructureToPtr(header, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.DriveManufacturer = metadata.DriveManufacturer; - imageInfo.DriveModel = metadata.DriveModel; - imageInfo.DriveFirmwareRevision = metadata.DriveFirmwareRevision; - imageInfo.DriveSerialNumber = metadata.DriveSerialNumber; - - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > ushort.MaxValue) - { - ErrorMessage = "Too many cylinders."; - return false; - } - - if(heads > ushort.MaxValue) - { - ErrorMessage = "Too many heads."; - return false; - } - - if(sectorsPerTrack > ushort.MaxValue) - { - ErrorMessage = "Too many sectors per track."; - return false; - } - - imageInfo.SectorsPerTrack = sectorsPerTrack; - imageInfo.Heads = heads; - imageInfo.Cylinders = cylinders; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - static byte[] ScrambleAtaString(string text, int length) - { - byte[] inbuf = Encoding.ASCII.GetBytes(text); - if(inbuf.Length % 2 != 0) - { - byte[] tmpbuf = new byte[inbuf.Length + 1]; - Array.Copy(inbuf, 0, tmpbuf, 0, inbuf.Length); - tmpbuf[tmpbuf.Length - 1] = 0x20; - inbuf = tmpbuf; - } - - byte[] outbuf = new byte[inbuf.Length]; - - for(int i = 0; i < length; i += 2) - { - outbuf[i] = inbuf[i + 1]; - outbuf[i + 1] = inbuf[i]; - } - - byte[] retBuf = new byte[length]; - for(int i = 0; i < length; i++) retBuf[i] = 0x20; - - Array.Copy(outbuf, 0, retBuf, 0, outbuf.Length >= length ? length : outbuf.Length); - return retBuf; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct RsIdeHeader - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] - public byte[] magic; - public byte revision; - public RsIdeFlags flags; - public ushort dataOff; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] - public byte[] reserved; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 106)] - public byte[] identify; - } - - [Flags] - enum RsIdeFlags : byte - { - HalfSectors = 1 - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Constants.cs b/DiscImageChef.DiscImages/RsIde/Constants.cs new file mode 100644 index 000000000..1187f0436 --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Constants.cs @@ -0,0 +1,39 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + readonly byte[] signature = {0x52, 0x53, 0x2D, 0x49, 0x44, 0x45, 0x1A}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Enums.cs b/DiscImageChef.DiscImages/RsIde/Enums.cs new file mode 100644 index 000000000..c19a9b05b --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Enums.cs @@ -0,0 +1,45 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + [Flags] + enum RsIdeFlags : byte + { + HalfSectors = 1 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Helpers.cs b/DiscImageChef.DiscImages/RsIde/Helpers.cs new file mode 100644 index 000000000..70932ed9c --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Helpers.cs @@ -0,0 +1,66 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Text; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + static byte[] ScrambleAtaString(string text, int length) + { + byte[] inbuf = Encoding.ASCII.GetBytes(text); + if(inbuf.Length % 2 != 0) + { + byte[] tmpbuf = new byte[inbuf.Length + 1]; + Array.Copy(inbuf, 0, tmpbuf, 0, inbuf.Length); + tmpbuf[tmpbuf.Length - 1] = 0x20; + inbuf = tmpbuf; + } + + byte[] outbuf = new byte[inbuf.Length]; + + for(int i = 0; i < length; i += 2) + { + outbuf[i] = inbuf[i + 1]; + outbuf[i + 1] = inbuf[i]; + } + + byte[] retBuf = new byte[length]; + for(int i = 0; i < length; i++) retBuf[i] = 0x20; + + Array.Copy(outbuf, 0, retBuf, 0, outbuf.Length >= length ? length : outbuf.Length); + return retBuf; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Identify.cs b/DiscImageChef.DiscImages/RsIde/Identify.cs new file mode 100644 index 000000000..0809d1db2 --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Identify.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + byte[] magic = new byte[7]; + stream.Read(magic, 0, magic.Length); + + return magic.SequenceEqual(signature); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Properties.cs b/DiscImageChef.DiscImages/RsIde/Properties.cs new file mode 100644 index 000000000..17d178060 --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Properties.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + public string Name => "RS-IDE Hard Disk Image"; + public Guid Id => new Guid("47C3E78D-2BE2-4BA5-AA6B-FEE27C86FC65"); + public ImageInfo Info => imageInfo; + + public string Format => "RS-IDE disk image"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new[] {MediaTagType.ATA_IDENTIFY}; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".ide"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Read.cs b/DiscImageChef.DiscImages/RsIde/Read.cs new file mode 100644 index 000000000..e56f613b9 --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Read.cs @@ -0,0 +1,139 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + byte[] hdrB = new byte[Marshal.SizeOf(typeof(RsIdeHeader))]; + stream.Read(hdrB, 0, hdrB.Length); + + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(RsIdeHeader))); + Marshal.Copy(hdrB, 0, hdrPtr, Marshal.SizeOf(typeof(RsIdeHeader))); + RsIdeHeader hdr = (RsIdeHeader)Marshal.PtrToStructure(hdrPtr, typeof(RsIdeHeader)); + Marshal.FreeHGlobal(hdrPtr); + + if(!hdr.magic.SequenceEqual(signature)) return false; + + dataOff = hdr.dataOff; + + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.SectorSize = (uint)(hdr.flags.HasFlag(RsIdeFlags.HalfSectors) ? 256 : 512); + imageInfo.ImageSize = (ulong)(stream.Length - dataOff); + imageInfo.Sectors = imageInfo.ImageSize / imageInfo.SectorSize; + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.Version = $"{hdr.revision >> 8}.{hdr.revision & 0x0F}"; + + if(!ArrayHelpers.ArrayIsNullOrEmpty(hdr.identify)) + { + identify = new byte[512]; + Array.Copy(hdr.identify, 0, identify, 0, hdr.identify.Length); + Decoders.ATA.Identify.IdentifyDevice? ataId = Decoders.ATA.Identify.Decode(identify); + + if(ataId.HasValue) + { + imageInfo.ReadableMediaTags.Add(MediaTagType.ATA_IDENTIFY); + imageInfo.Cylinders = ataId.Value.Cylinders; + imageInfo.Heads = ataId.Value.Heads; + imageInfo.SectorsPerTrack = ataId.Value.SectorsPerCard; + imageInfo.DriveFirmwareRevision = ataId.Value.FirmwareRevision; + imageInfo.DriveModel = ataId.Value.Model; + imageInfo.DriveSerialNumber = ataId.Value.SerialNumber; + imageInfo.MediaSerialNumber = ataId.Value.MediaSerial; + imageInfo.MediaManufacturer = ataId.Value.MediaManufacturer; + } + } + + if(imageInfo.Cylinders == 0 || imageInfo.Heads == 0 || imageInfo.SectorsPerTrack == 0) + { + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + } + + rsIdeImageFilter = imageFilter; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Stream stream = rsIdeImageFilter.GetDataForkStream(); + + stream.Seek((long)(dataOff + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + + return buffer; + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + if(!imageInfo.ReadableMediaTags.Contains(tag) || tag != MediaTagType.ATA_IDENTIFY) + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + byte[] buffer = new byte[512]; + Array.Copy(identify, 0, buffer, 0, 512); + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/RsIde.cs b/DiscImageChef.DiscImages/RsIde/RsIde.cs new file mode 100644 index 000000000..3e0573e43 --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/RsIde.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : RsIde.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde : IWritableImage + { + ushort dataOff; + byte[] identify; + ImageInfo imageInfo; + IFilter rsIdeImageFilter; + FileStream writingStream; + + public RsIde() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Structs.cs b/DiscImageChef.DiscImages/RsIde/Structs.cs new file mode 100644 index 000000000..ba908f94a --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Structs.cs @@ -0,0 +1,53 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct RsIdeHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] + public byte[] magic; + public byte revision; + public RsIdeFlags flags; + public ushort dataOff; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] + public byte[] reserved; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 106)] + public byte[] identify; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Unsupported.cs b/DiscImageChef.DiscImages/RsIde/Unsupported.cs new file mode 100644 index 000000000..bff4811f5 --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Unsupported.cs @@ -0,0 +1,133 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/RsIde/Write.cs b/DiscImageChef.DiscImages/RsIde/Write.cs new file mode 100644 index 000000000..bceea9f5b --- /dev/null +++ b/DiscImageChef.DiscImages/RsIde/Write.cs @@ -0,0 +1,345 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes RS-IDE disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; +using Version = DiscImageChef.CommonTypes.Interop.Version; + +namespace DiscImageChef.DiscImages +{ + public partial class RsIde + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 256 && sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors > 63 * 16 * 1024) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(tag != MediaTagType.ATA_IDENTIFY) + { + ErrorMessage = $"Unsupported media tag {tag}."; + return false; + } + + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + identify = new byte[106]; + Array.Copy(data, 0, identify, 0, 106); + return true; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream + .Seek((long)((ulong)Marshal.SizeOf(typeof(RsIdeHeader)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream + .Seek((long)((ulong)Marshal.SizeOf(typeof(RsIdeHeader)) + sectorAddress * imageInfo.SectorSize), + SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(imageInfo.Cylinders == 0) + { + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + while(imageInfo.Cylinders == 0) + { + imageInfo.Heads--; + + if(imageInfo.Heads == 0) + { + imageInfo.SectorsPerTrack--; + imageInfo.Heads = 16; + } + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); + + if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; + } + } + + RsIdeHeader header = new RsIdeHeader + { + magic = signature, + identify = new byte[106], + dataOff = (ushort)Marshal.SizeOf(typeof(RsIdeHeader)), + revision = 1, + reserved = new byte[11] + }; + if(imageInfo.SectorSize == 256) header.flags = RsIdeFlags.HalfSectors; + + if(identify == null) + { + Decoders.ATA.Identify.IdentifyDevice ataId = new Decoders.ATA.Identify.IdentifyDevice + { + GeneralConfiguration = + Decoders.ATA.Identify.GeneralConfigurationBit.UltraFastIDE | + Decoders.ATA.Identify.GeneralConfigurationBit.Fixed | + Decoders.ATA.Identify.GeneralConfigurationBit.NotMFM | + Decoders.ATA.Identify.GeneralConfigurationBit.SoftSector, + Cylinders = (ushort)imageInfo.Cylinders, + Heads = (ushort)imageInfo.Heads, + SectorsPerTrack = (ushort)imageInfo.SectorsPerTrack, + VendorWord47 = 0x80, + Capabilities = + Decoders.ATA.Identify.CapabilitiesBit.DMASupport | Decoders.ATA.Identify.CapabilitiesBit.IORDY | + Decoders.ATA.Identify.CapabilitiesBit.LBASupport, + ExtendedIdentify = Decoders.ATA.Identify.ExtendedIdentifyBit.Words54to58Valid, + CurrentCylinders = (ushort)imageInfo.Cylinders, + CurrentHeads = (ushort)imageInfo.Heads, + CurrentSectorsPerTrack = (ushort)imageInfo.SectorsPerTrack, + CurrentSectors = (uint)imageInfo.Sectors, + LBASectors = (uint)imageInfo.Sectors, + DMASupported = Decoders.ATA.Identify.TransferMode.Mode0, + DMAActive = Decoders.ATA.Identify.TransferMode.Mode0 + }; + + if(string.IsNullOrEmpty(imageInfo.DriveManufacturer)) imageInfo.DriveManufacturer = "DiscImageChef"; + + if(string.IsNullOrEmpty(imageInfo.DriveModel)) imageInfo.DriveModel = ""; + + if(string.IsNullOrEmpty(imageInfo.DriveFirmwareRevision)) Version.GetVersion(); + + if(string.IsNullOrEmpty(imageInfo.DriveSerialNumber)) + imageInfo.DriveSerialNumber = $"{new Random().NextDouble():16X}"; + + byte[] ataIdBytes = new byte[Marshal.SizeOf(ataId)]; + IntPtr ptr = Marshal.AllocHGlobal(512); + Marshal.StructureToPtr(ataId, ptr, true); + Marshal.Copy(ptr, ataIdBytes, 0, Marshal.SizeOf(ataId)); + Marshal.FreeHGlobal(ptr); + + Array.Copy(ScrambleAtaString(imageInfo.DriveManufacturer + " " + imageInfo.DriveModel, 40), 0, + ataIdBytes, 27 * 2, 40); + Array.Copy(ScrambleAtaString(imageInfo.DriveFirmwareRevision, 8), 0, ataIdBytes, 23 * 2, 8); + Array.Copy(ScrambleAtaString(imageInfo.DriveSerialNumber, 20), 0, ataIdBytes, 10 * 2, 20); + Array.Copy(ataIdBytes, 0, header.identify, 0, 106); + } + else Array.Copy(identify, 0, header.identify, 0, 106); + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.StructureToPtr(header, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.DriveManufacturer = metadata.DriveManufacturer; + imageInfo.DriveModel = metadata.DriveModel; + imageInfo.DriveFirmwareRevision = metadata.DriveFirmwareRevision; + imageInfo.DriveSerialNumber = metadata.DriveSerialNumber; + + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > ushort.MaxValue) + { + ErrorMessage = "Too many cylinders."; + return false; + } + + if(heads > ushort.MaxValue) + { + ErrorMessage = "Too many heads."; + return false; + } + + if(sectorsPerTrack > ushort.MaxValue) + { + ErrorMessage = "Too many sectors per track."; + return false; + } + + imageInfo.SectorsPerTrack = sectorsPerTrack; + imageInfo.Heads = heads; + imageInfo.Cylinders = cylinders; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF.cs b/DiscImageChef.DiscImages/SaveDskF.cs deleted file mode 100644 index 21544d43f..000000000 --- a/DiscImageChef.DiscImages/SaveDskF.cs +++ /dev/null @@ -1,624 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : SaveDskF.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages IBM SaveDskF disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class SaveDskF : IWritableImage - { - const ushort SDF_MAGIC_OLD = 0x58AA; - const ushort SDF_MAGIC = 0x59AA; - const ushort SDF_MAGIC_COMPRESSED = 0x5AAA; - uint calculatedChk; - byte[] decodedDisk; - - SaveDskFHeader header; - ImageInfo imageInfo; - FileStream writingStream; - - public SaveDskF() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "IBM SaveDskF"; - public Guid Id => new Guid("288CE058-1A51-4034-8C45-5A256CAE1461"); - public ImageInfo Info => imageInfo; - - public string Format => "IBM SaveDskF"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < 41) return false; - - byte[] hdr = new byte[40]; - stream.Read(hdr, 0, 40); - - header = new SaveDskFHeader(); - IntPtr hdrPtr = Marshal.AllocHGlobal(40); - Marshal.Copy(hdr, 0, hdrPtr, 40); - header = (SaveDskFHeader)Marshal.PtrToStructure(hdrPtr, typeof(SaveDskFHeader)); - Marshal.FreeHGlobal(hdrPtr); - - return (header.magic == SDF_MAGIC || header.magic == SDF_MAGIC_COMPRESSED || - header.magic == SDF_MAGIC_OLD) && header.fatCopies <= 2 && header.padding == 0 && - header.commentOffset < stream.Length && - header.dataOffset < stream.Length; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - byte[] hdr = new byte[40]; - - stream.Read(hdr, 0, 40); - header = new SaveDskFHeader(); - IntPtr hdrPtr = Marshal.AllocHGlobal(40); - Marshal.Copy(hdr, 0, hdrPtr, 40); - header = (SaveDskFHeader)Marshal.PtrToStructure(hdrPtr, typeof(SaveDskFHeader)); - Marshal.FreeHGlobal(hdrPtr); - - DicConsole.DebugWriteLine("SaveDskF plugin", "header.magic = 0x{0:X4}", header.magic); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.mediaType = 0x{0:X2}", header.mediaType); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorSize = {0}", header.sectorSize); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.clusterMask = {0}", header.clusterMask); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.clusterShift = {0}", header.clusterShift); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.reservedSectors = {0}", header.reservedSectors); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.fatCopies = {0}", header.fatCopies); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.rootEntries = {0}", header.rootEntries); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.firstCluster = {0}", header.firstCluster); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.clustersCopied = {0}", header.clustersCopied); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsPerFat = {0}", header.sectorsPerFat); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.checksum = 0x{0:X8}", header.checksum); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.cylinders = {0}", header.cylinders); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.heads = {0}", header.heads); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsPerTrack = {0}", header.sectorsPerTrack); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.padding = {0}", header.padding); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsCopied = {0}", header.sectorsCopied); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.commentOffset = {0}", header.commentOffset); - DicConsole.DebugWriteLine("SaveDskF plugin", "header.dataOffset = {0}", header.dataOffset); - - if(header.dataOffset == 0 && header.magic == SDF_MAGIC_OLD) header.dataOffset = 512; - - byte[] cmt = new byte[header.dataOffset - header.commentOffset]; - stream.Seek(header.commentOffset, SeekOrigin.Begin); - stream.Read(cmt, 0, cmt.Length); - if(cmt.Length > 1) imageInfo.Comments = StringHandlers.CToString(cmt, Encoding.GetEncoding("ibm437")); - - calculatedChk = 0; - stream.Seek(0, SeekOrigin.Begin); - - int b; - do - { - b = stream.ReadByte(); - if(b >= 0) calculatedChk += (uint)b; - } - while(b >= 0); - - DicConsole.DebugWriteLine("SaveDskF plugin", "Calculated checksum = 0x{0:X8}, {1}", calculatedChk, - calculatedChk == header.checksum); - - imageInfo.Application = "SaveDskF"; - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = imageFilter.GetFilename(); - imageInfo.ImageSize = (ulong)(stream.Length - header.dataOffset); - imageInfo.Sectors = (ulong)(header.sectorsPerTrack * header.heads * header.cylinders); - imageInfo.SectorSize = header.sectorSize; - - imageInfo.MediaType = Geometry.GetMediaType((header.cylinders, (byte)header.heads, header.sectorsPerTrack, - header.sectorSize, MediaEncoding.MFM, false)); - - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - - DicConsole.VerboseWriteLine("SaveDskF image contains a disk of type {0}", imageInfo.MediaType); - if(!string.IsNullOrEmpty(imageInfo.Comments)) - DicConsole.VerboseWriteLine("SaveDskF comments: {0}", imageInfo.Comments); - - // TODO: Support compressed images - if(header.magic == SDF_MAGIC_COMPRESSED) - throw new - FeatureSupportedButNotImplementedImageException("Compressed SaveDskF images are not supported."); - - // SaveDskF only ommits ending clusters, leaving no gaps behind, so reading all data we have... - stream.Seek(header.dataOffset, SeekOrigin.Begin); - decodedDisk = new byte[imageInfo.Sectors * imageInfo.SectorSize]; - stream.Read(decodedDisk, 0, (int)(stream.Length - header.dataOffset)); - - imageInfo.Cylinders = header.cylinders; - imageInfo.Heads = header.heads; - imageInfo.SectorsPerTrack = header.sectorsPerTrack; - - return true; - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - - for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifyMediaImage() - { - return calculatedChk == header.checksum; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Array.Copy(decodedDisk, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, - length * imageInfo.SectorSize); - - return buffer; - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - // TODO: Test with real hardware to see real supported media - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, - MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_SS_DD_11, MediaType.DMF, - MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, - MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, - MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, - MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, - MediaType.FDFORMAT_525_HD, MediaType.RX50, MediaType.XDF_35, MediaType.XDF_525 - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".dsk"}; - - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(512 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(512 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(!string.IsNullOrWhiteSpace(imageInfo.Comments)) - { - byte[] commentsBytes = Encoding.GetEncoding("ibm437").GetBytes(imageInfo.Comments); - header.commentOffset = (ushort)Marshal.SizeOf(header); - writingStream.Seek(header.commentOffset, SeekOrigin.Begin); - writingStream.Write(commentsBytes, 0, - commentsBytes.Length >= 512 - header.commentOffset - ? 512 - header.commentOffset - : commentsBytes.Length); - } - - byte[] hdr = new byte[Marshal.SizeOf(header)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.StructureToPtr(header, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - header.checksum = 0; - writingStream.Seek(0, SeekOrigin.Begin); - - int b; - do - { - b = writingStream.ReadByte(); - if(b >= 0) header.checksum += (uint)b; - } - while(b >= 0); - - hdr = new byte[Marshal.SizeOf(header)]; - hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); - Marshal.StructureToPtr(header, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.Comments = metadata.Comments; - return true; - } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize == 0) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors > ushort.MaxValue) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool - variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); - - header = new SaveDskFHeader - { - cylinders = geometry.cylinders, - dataOffset = 512, - heads = geometry.heads, - magic = SDF_MAGIC, - sectorsCopied = (ushort)sectors, - sectorsPerTrack = geometry.sectorsPerTrack, - sectorSize = (ushort)sectorSize - }; - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is set by media type - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct SaveDskFHeader - { - /// <summary>0x00 magic number</summary> - public ushort magic; - /// <summary>0x02 media type from FAT</summary> - public ushort mediaType; - /// <summary>0x04 bytes per sector</summary> - public ushort sectorSize; - /// <summary>0x06 sectors per cluster - 1</summary> - public byte clusterMask; - /// <summary>0x07 log2(cluster / sector)</summary> - public byte clusterShift; - /// <summary>0x08 reserved sectors</summary> - public ushort reservedSectors; - /// <summary>0x0A copies of FAT</summary> - public byte fatCopies; - /// <summary>0x0B entries in root directory</summary> - public ushort rootEntries; - /// <summary>0x0D first cluster</summary> - public ushort firstCluster; - /// <summary>0x0F clusters present in image</summary> - public ushort clustersCopied; - /// <summary>0x11 sectors per FAT</summary> - public byte sectorsPerFat; - /// <summary>0x12 sector number of root directory</summary> - public ushort rootDirectorySector; - /// <summary>0x14 sum of all image bytes</summary> - public uint checksum; - /// <summary>0x18 cylinders</summary> - public ushort cylinders; - /// <summary>0x1A heads</summary> - public ushort heads; - /// <summary>0x1C sectors per track</summary> - public ushort sectorsPerTrack; - /// <summary>0x1E always zero</summary> - public uint padding; - /// <summary>0x22 sectors present in image</summary> - public ushort sectorsCopied; - /// <summary>0x24 offset to comment</summary> - public ushort commentOffset; - /// <summary>0x26 offset to data</summary> - public ushort dataOffset; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/Constants.cs b/DiscImageChef.DiscImages/SaveDskF/Constants.cs new file mode 100644 index 000000000..c4bdba420 --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/Constants.cs @@ -0,0 +1,41 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for IBM SaveDskF disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF + { + const ushort SDF_MAGIC_OLD = 0x58AA; + const ushort SDF_MAGIC = 0x59AA; + const ushort SDF_MAGIC_COMPRESSED = 0x5AAA; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/Identify.cs b/DiscImageChef.DiscImages/SaveDskF/Identify.cs new file mode 100644 index 000000000..35e82c10b --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/Identify.cs @@ -0,0 +1,63 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Anex86 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < 41) return false; + + byte[] hdr = new byte[40]; + stream.Read(hdr, 0, 40); + + header = new SaveDskFHeader(); + IntPtr hdrPtr = Marshal.AllocHGlobal(40); + Marshal.Copy(hdr, 0, hdrPtr, 40); + header = (SaveDskFHeader)Marshal.PtrToStructure(hdrPtr, typeof(SaveDskFHeader)); + Marshal.FreeHGlobal(hdrPtr); + + return (header.magic == SDF_MAGIC || header.magic == SDF_MAGIC_COMPRESSED || + header.magic == SDF_MAGIC_OLD) && header.fatCopies <= 2 && header.padding == 0 && + header.commentOffset < stream.Length && + header.dataOffset < stream.Length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/Properties.cs b/DiscImageChef.DiscImages/SaveDskF/Properties.cs new file mode 100644 index 000000000..db39a9757 --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/Properties.cs @@ -0,0 +1,83 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for IBM SaveDskF disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF + { + public string Name => "IBM SaveDskF"; + public Guid Id => new Guid("288CE058-1A51-4034-8C45-5A256CAE1461"); + public ImageInfo Info => imageInfo; + + public string Format => "IBM SaveDskF"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + // TODO: Test with real hardware to see real supported media + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, MediaType.Apricot_35, MediaType.ATARI_35_DS_DD, + MediaType.ATARI_35_DS_DD_11, MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_SS_DD_11, MediaType.DMF, + MediaType.DMF_82, MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_ED, + MediaType.DOS_35_HD, MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, MediaType.DOS_525_DS_DD_8, + MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, + MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, MediaType.FDFORMAT_525_DD, + MediaType.FDFORMAT_525_HD, MediaType.RX50, MediaType.XDF_35, MediaType.XDF_525 + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".dsk"}; + + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/Read.cs b/DiscImageChef.DiscImages/SaveDskF/Read.cs new file mode 100644 index 000000000..6bc9a9084 --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/Read.cs @@ -0,0 +1,162 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads IBM SaveDskF disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + byte[] hdr = new byte[40]; + + stream.Read(hdr, 0, 40); + header = new SaveDskFHeader(); + IntPtr hdrPtr = Marshal.AllocHGlobal(40); + Marshal.Copy(hdr, 0, hdrPtr, 40); + header = (SaveDskFHeader)Marshal.PtrToStructure(hdrPtr, typeof(SaveDskFHeader)); + Marshal.FreeHGlobal(hdrPtr); + + DicConsole.DebugWriteLine("SaveDskF plugin", "header.magic = 0x{0:X4}", header.magic); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.mediaType = 0x{0:X2}", header.mediaType); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorSize = {0}", header.sectorSize); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.clusterMask = {0}", header.clusterMask); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.clusterShift = {0}", header.clusterShift); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.reservedSectors = {0}", header.reservedSectors); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.fatCopies = {0}", header.fatCopies); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.rootEntries = {0}", header.rootEntries); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.firstCluster = {0}", header.firstCluster); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.clustersCopied = {0}", header.clustersCopied); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsPerFat = {0}", header.sectorsPerFat); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.checksum = 0x{0:X8}", header.checksum); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.cylinders = {0}", header.cylinders); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.heads = {0}", header.heads); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsPerTrack = {0}", header.sectorsPerTrack); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.padding = {0}", header.padding); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsCopied = {0}", header.sectorsCopied); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.commentOffset = {0}", header.commentOffset); + DicConsole.DebugWriteLine("SaveDskF plugin", "header.dataOffset = {0}", header.dataOffset); + + if(header.dataOffset == 0 && header.magic == SDF_MAGIC_OLD) header.dataOffset = 512; + + byte[] cmt = new byte[header.dataOffset - header.commentOffset]; + stream.Seek(header.commentOffset, SeekOrigin.Begin); + stream.Read(cmt, 0, cmt.Length); + if(cmt.Length > 1) imageInfo.Comments = StringHandlers.CToString(cmt, Encoding.GetEncoding("ibm437")); + + calculatedChk = 0; + stream.Seek(0, SeekOrigin.Begin); + + int b; + do + { + b = stream.ReadByte(); + if(b >= 0) calculatedChk += (uint)b; + } + while(b >= 0); + + DicConsole.DebugWriteLine("SaveDskF plugin", "Calculated checksum = 0x{0:X8}, {1}", calculatedChk, + calculatedChk == header.checksum); + + imageInfo.Application = "SaveDskF"; + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = imageFilter.GetFilename(); + imageInfo.ImageSize = (ulong)(stream.Length - header.dataOffset); + imageInfo.Sectors = (ulong)(header.sectorsPerTrack * header.heads * header.cylinders); + imageInfo.SectorSize = header.sectorSize; + + imageInfo.MediaType = Geometry.GetMediaType((header.cylinders, (byte)header.heads, header.sectorsPerTrack, + header.sectorSize, MediaEncoding.MFM, false)); + + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + + DicConsole.VerboseWriteLine("SaveDskF image contains a disk of type {0}", imageInfo.MediaType); + if(!string.IsNullOrEmpty(imageInfo.Comments)) + DicConsole.VerboseWriteLine("SaveDskF comments: {0}", imageInfo.Comments); + + // TODO: Support compressed images + if(header.magic == SDF_MAGIC_COMPRESSED) + throw new + FeatureSupportedButNotImplementedImageException("Compressed SaveDskF images are not supported."); + + // SaveDskF only ommits ending clusters, leaving no gaps behind, so reading all data we have... + stream.Seek(header.dataOffset, SeekOrigin.Begin); + decodedDisk = new byte[imageInfo.Sectors * imageInfo.SectorSize]; + stream.Read(decodedDisk, 0, (int)(stream.Length - header.dataOffset)); + + imageInfo.Cylinders = header.cylinders; + imageInfo.Heads = header.heads; + imageInfo.SectorsPerTrack = header.sectorsPerTrack; + + return true; + } + + public bool? VerifyMediaImage() + { + return calculatedChk == header.checksum; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Array.Copy(decodedDisk, (int)sectorAddress * imageInfo.SectorSize, buffer, 0, + length * imageInfo.SectorSize); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/SaveDskF.cs b/DiscImageChef.DiscImages/SaveDskF/SaveDskF.cs new file mode 100644 index 000000000..31651744a --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/SaveDskF.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : SaveDskF.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages IBM SaveDskF disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF : IWritableImage + { + uint calculatedChk; + byte[] decodedDisk; + SaveDskFHeader header; + ImageInfo imageInfo; + FileStream writingStream; + + public SaveDskF() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/Structs.cs b/DiscImageChef.DiscImages/SaveDskF/Structs.cs new file mode 100644 index 000000000..59442386c --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/Structs.cs @@ -0,0 +1,84 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for IBM SaveDskF disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SaveDskFHeader + { + /// <summary>0x00 magic number</summary> + public ushort magic; + /// <summary>0x02 media type from FAT</summary> + public ushort mediaType; + /// <summary>0x04 bytes per sector</summary> + public ushort sectorSize; + /// <summary>0x06 sectors per cluster - 1</summary> + public byte clusterMask; + /// <summary>0x07 log2(cluster / sector)</summary> + public byte clusterShift; + /// <summary>0x08 reserved sectors</summary> + public ushort reservedSectors; + /// <summary>0x0A copies of FAT</summary> + public byte fatCopies; + /// <summary>0x0B entries in root directory</summary> + public ushort rootEntries; + /// <summary>0x0D first cluster</summary> + public ushort firstCluster; + /// <summary>0x0F clusters present in image</summary> + public ushort clustersCopied; + /// <summary>0x11 sectors per FAT</summary> + public byte sectorsPerFat; + /// <summary>0x12 sector number of root directory</summary> + public ushort rootDirectorySector; + /// <summary>0x14 sum of all image bytes</summary> + public uint checksum; + /// <summary>0x18 cylinders</summary> + public ushort cylinders; + /// <summary>0x1A heads</summary> + public ushort heads; + /// <summary>0x1C sectors per track</summary> + public ushort sectorsPerTrack; + /// <summary>0x1E always zero</summary> + public uint padding; + /// <summary>0x22 sectors present in image</summary> + public ushort sectorsCopied; + /// <summary>0x24 offset to comment</summary> + public ushort commentOffset; + /// <summary>0x26 offset to data</summary> + public ushort dataOffset; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/Unsupported.cs b/DiscImageChef.DiscImages/SaveDskF/Unsupported.cs new file mode 100644 index 000000000..b12f7e696 --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/Unsupported.cs @@ -0,0 +1,139 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by IBM SaveDskF disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF + { + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i); + + return null; + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SaveDskF/Write.cs b/DiscImageChef.DiscImages/SaveDskF/Write.cs new file mode 100644 index 000000000..fff0c2aa2 --- /dev/null +++ b/DiscImageChef.DiscImages/SaveDskF/Write.cs @@ -0,0 +1,267 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes IBM SaveDskF disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class SaveDskF + { + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(512 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(512 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(!string.IsNullOrWhiteSpace(imageInfo.Comments)) + { + byte[] commentsBytes = Encoding.GetEncoding("ibm437").GetBytes(imageInfo.Comments); + header.commentOffset = (ushort)Marshal.SizeOf(header); + writingStream.Seek(header.commentOffset, SeekOrigin.Begin); + writingStream.Write(commentsBytes, 0, + commentsBytes.Length >= 512 - header.commentOffset + ? 512 - header.commentOffset + : commentsBytes.Length); + } + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.StructureToPtr(header, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + header.checksum = 0; + writingStream.Seek(0, SeekOrigin.Begin); + + int b; + do + { + b = writingStream.ReadByte(); + if(b >= 0) header.checksum += (uint)b; + } + while(b >= 0); + + hdr = new byte[Marshal.SizeOf(header)]; + hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.StructureToPtr(header, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.Comments = metadata.Comments; + return true; + } + + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize == 0) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors > ushort.MaxValue) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + (ushort cylinders, byte heads, ushort sectorsPerTrack, uint bytesPerSector, MediaEncoding encoding, bool + variableSectorsPerTrack, MediaType type) geometry = Geometry.GetGeometry(mediaType); + + header = new SaveDskFHeader + { + cylinders = geometry.cylinders, + dataOffset = 512, + heads = geometry.heads, + magic = SDF_MAGIC, + sectorsCopied = (ushort)sectors, + sectorsPerTrack = geometry.sectorsPerTrack, + sectorSize = (ushort)sectorSize + }; + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is set by media type + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/Constants.cs b/DiscImageChef.DiscImages/SuperCardPro/Constants.cs new file mode 100644 index 000000000..d067d5a9d --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/Constants.cs @@ -0,0 +1,50 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro + { + /// <summary> + /// SuperCardPro footer signature: "FPCS" + /// </summary> + const uint FOOTER_SIGNATURE = 0x53435046; + /// <summary> + /// SuperCardPro header signature: "SCP" + /// </summary> + readonly byte[] scpSignature = {0x53, 0x43, 0x50}; + /// <summary> + /// SuperCardPro track header signature: "TRK" + /// </summary> + readonly byte[] trkSignature = {0x54, 0x52, 0x4B}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/Enums.cs b/DiscImageChef.DiscImages/SuperCardPro/Enums.cs new file mode 100644 index 000000000..2bf3c29ca --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/Enums.cs @@ -0,0 +1,94 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro + { + public enum ScpDiskType : byte + { + Commodore64 = 0x00, + CommodoreAmiga = 0x04, + AtariFMSS = 0x10, + AtariFMDS = 0x11, + AtariFSEx = 0x12, + AtariSTSS = 0x14, + AtariSTDS = 0x15, + AppleII = 0x20, + AppleIIPro = 0x21, + Apple400K = 0x24, + Apple800K = 0x25, + Apple144 = 0x26, + PC360K = 0x30, + PC720K = 0x31, + PC12M = 0x32, + PC144M = 0x33, + TandySSSD = 0x40, + TandySSDD = 0x41, + TandyDSSD = 0x42, + TandyDSDD = 0x43, + Ti994A = 0x50, + RolandD20 = 0x60 + } + + [Flags] + public enum ScpFlags : byte + { + /// <summary> + /// If set flux starts at index pulse + /// </summary> + Index = 0x00, + /// <summary> + /// If set drive is 96tpi + /// </summary> + Tpi = 0x02, + /// <summary> + /// If set drive is 360rpm + /// </summary> + Rpm = 0x04, + /// <summary> + /// If set image contains normalized data + /// </summary> + Normalized = 0x08, + /// <summary> + /// If set image is read/write capable + /// </summary> + Writable = 0x10, + /// <summary> + /// If set, image has footer + /// </summary> + HasFooter = 0x20 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/Helpers.cs b/DiscImageChef.DiscImages/SuperCardPro/Helpers.cs new file mode 100644 index 000000000..01249dc46 --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/Helpers.cs @@ -0,0 +1,58 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Text; + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro + { + static string ReadPStringUtf8(Stream stream, uint position) + { + if(position == 0) return null; + + stream.Position = position; + byte[] lenB = new byte[2]; + stream.Read(lenB, 0, 2); + ushort len = BitConverter.ToUInt16(lenB, 0); + + if(len == 0 || len + stream.Position >= stream.Length) return null; + + byte[] str = new byte[len]; + stream.Read(str, 0, len); + + return Encoding.UTF8.GetString(str); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/Identify.cs b/DiscImageChef.DiscImages/SuperCardPro/Identify.cs new file mode 100644 index 000000000..971cecc37 --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/Identify.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro + { + public bool Identify(IFilter imageFilter) + { + Header = new ScpHeader(); + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < Marshal.SizeOf(Header)) return false; + + byte[] hdr = new byte[Marshal.SizeOf(Header)]; + stream.Read(hdr, 0, Marshal.SizeOf(Header)); + + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Header)); + Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(Header)); + Header = (ScpHeader)Marshal.PtrToStructure(hdrPtr, typeof(ScpHeader)); + Marshal.FreeHGlobal(hdrPtr); + + return scpSignature.SequenceEqual(Header.signature); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/Properties.cs b/DiscImageChef.DiscImages/SuperCardPro/Properties.cs new file mode 100644 index 000000000..d1f88ec49 --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/Properties.cs @@ -0,0 +1,63 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro + { + public ImageInfo Info => imageInfo; + + public string Name => "SuperCardPro"; + public Guid Id => new Guid("C5D3182E-1D45-4767-A205-E6E5C83444DC"); + + public string Format => "SuperCardPro"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro.cs b/DiscImageChef.DiscImages/SuperCardPro/Read.cs similarity index 61% rename from DiscImageChef.DiscImages/SuperCardPro.cs rename to DiscImageChef.DiscImages/SuperCardPro/Read.cs index 944d9a2c2..f7dc12a3b 100644 --- a/DiscImageChef.DiscImages/SuperCardPro.cs +++ b/DiscImageChef.DiscImages/SuperCardPro/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : SuperCardPro.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages SuperCardPro flux images. +// Reads SuperCardPro flux images. // // --[ License ] -------------------------------------------------------------- // @@ -35,155 +35,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; namespace DiscImageChef.DiscImages { - public class SuperCardPro : IMediaImage + public partial class SuperCardPro { - public enum ScpDiskType : byte - { - Commodore64 = 0x00, - CommodoreAmiga = 0x04, - AtariFMSS = 0x10, - AtariFMDS = 0x11, - AtariFSEx = 0x12, - AtariSTSS = 0x14, - AtariSTDS = 0x15, - AppleII = 0x20, - AppleIIPro = 0x21, - Apple400K = 0x24, - Apple800K = 0x25, - Apple144 = 0x26, - PC360K = 0x30, - PC720K = 0x31, - PC12M = 0x32, - PC144M = 0x33, - TandySSSD = 0x40, - TandySSDD = 0x41, - TandyDSSD = 0x42, - TandyDSDD = 0x43, - Ti994A = 0x50, - RolandD20 = 0x60 - } - - [Flags] - public enum ScpFlags : byte - { - /// <summary> - /// If set flux starts at index pulse - /// </summary> - Index = 0x00, - /// <summary> - /// If set drive is 96tpi - /// </summary> - Tpi = 0x02, - /// <summary> - /// If set drive is 360rpm - /// </summary> - Rpm = 0x04, - /// <summary> - /// If set image contains normalized data - /// </summary> - Normalized = 0x08, - /// <summary> - /// If set image is read/write capable - /// </summary> - Writable = 0x10, - /// <summary> - /// If set, image has footer - /// </summary> - HasFooter = 0x20 - } - - /// <summary> - /// SuperCardPro footer signature: "FPCS" - /// </summary> - const uint FOOTER_SIGNATURE = 0x53435046; - - /// <summary> - /// SuperCardPro header signature: "SCP" - /// </summary> - readonly byte[] scpSignature = {0x53, 0x43, 0x50}; - /// <summary> - /// SuperCardPro track header signature: "TRK" - /// </summary> - readonly byte[] trkSignature = {0x54, 0x52, 0x4B}; - - // TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0 - public ScpHeader Header; - ImageInfo imageInfo; - Stream scpStream; - public Dictionary<byte, TrackHeader> ScpTracks; - - public SuperCardPro() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "SuperCardPro"; - public Guid Id => new Guid("C5D3182E-1D45-4767-A205-E6E5C83444DC"); - - public string Format => "SuperCardPro"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Header = new ScpHeader(); - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - if(stream.Length < Marshal.SizeOf(Header)) return false; - - byte[] hdr = new byte[Marshal.SizeOf(Header)]; - stream.Read(hdr, 0, Marshal.SizeOf(Header)); - - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Header)); - Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(Header)); - Header = (ScpHeader)Marshal.PtrToStructure(hdrPtr, typeof(ScpHeader)); - Marshal.FreeHGlobal(hdrPtr); - - return scpSignature.SequenceEqual(Header.signature); - } - public bool Open(IFilter imageFilter) { Header = new ScpHeader(); @@ -399,11 +258,6 @@ namespace DiscImageChef.DiscImages throw new NotImplementedException("Flux decoding is not yet implemented."); } - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) { throw new NotImplementedException("Flux decoding is not yet implemented."); @@ -434,124 +288,5 @@ namespace DiscImageChef.DiscImages return Header.checksum == sum; } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - static string ReadPStringUtf8(Stream stream, uint position) - { - if(position == 0) return null; - - stream.Position = position; - byte[] lenB = new byte[2]; - stream.Read(lenB, 0, 2); - ushort len = BitConverter.ToUInt16(lenB, 0); - - if(len == 0 || len + stream.Position >= stream.Length) return null; - - byte[] str = new byte[len]; - stream.Read(str, 0, len); - - return Encoding.UTF8.GetString(str); - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct ScpHeader - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] signature; - public byte version; - public ScpDiskType type; - public byte revolutions; - public byte start; - public byte end; - public ScpFlags flags; - public byte bitCellEncoding; - public byte heads; - public byte reserved; - public uint checksum; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 168)] - public uint[] offsets; - } - - public struct TrackHeader - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] Signature; - public byte TrackNumber; - public TrackEntry[] Entries; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct TrackEntry - { - public uint indexTime; - public uint trackLength; - public uint dataOffset; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct ScpFooter - { - public uint manufacturerOffset; - public uint modelOffset; - public uint serialOffset; - public uint creatorOffset; - public uint applicationOffset; - public uint commentsOffset; - public long creationTime; - public long modificationTime; - public byte applicationVersion; - public byte hardwareVersion; - public byte firmwareVersion; - public byte imageVersion; - public uint signature; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/Structs.cs b/DiscImageChef.DiscImages/SuperCardPro/Structs.cs new file mode 100644 index 000000000..ff707b8ea --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/Structs.cs @@ -0,0 +1,92 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ScpHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] signature; + public byte version; + public ScpDiskType type; + public byte revolutions; + public byte start; + public byte end; + public ScpFlags flags; + public byte bitCellEncoding; + public byte heads; + public byte reserved; + public uint checksum; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 168)] + public uint[] offsets; + } + + public struct TrackHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] Signature; + public byte TrackNumber; + public TrackEntry[] Entries; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TrackEntry + { + public uint indexTime; + public uint trackLength; + public uint dataOffset; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ScpFooter + { + public uint manufacturerOffset; + public uint modelOffset; + public uint serialOffset; + public uint creatorOffset; + public uint applicationOffset; + public uint commentsOffset; + public long creationTime; + public long modificationTime; + public byte applicationVersion; + public byte hardwareVersion; + public byte firmwareVersion; + public byte imageVersion; + public uint signature; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/SuperCardPro.cs b/DiscImageChef.DiscImages/SuperCardPro/SuperCardPro.cs new file mode 100644 index 000000000..483223b42 --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/SuperCardPro.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : SuperCardPro.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro : IMediaImage + { + // TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0 + public ScpHeader Header; + ImageInfo imageInfo; + Stream scpStream; + public Dictionary<byte, TrackHeader> ScpTracks; + + public SuperCardPro() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/SuperCardPro/Unsupported.cs b/DiscImageChef.DiscImages/SuperCardPro/Unsupported.cs new file mode 100644 index 000000000..df7733171 --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro/Unsupported.cs @@ -0,0 +1,93 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by SuperCardPro flux images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class SuperCardPro + { + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/T98.cs b/DiscImageChef.DiscImages/T98.cs deleted file mode 100644 index 9263d31e4..000000000 --- a/DiscImageChef.DiscImages/T98.cs +++ /dev/null @@ -1,452 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : T98.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages T98 disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - public class T98 : IWritableImage - { - ImageInfo imageInfo; - IFilter t98ImageFilter; - FileStream writingStream; - - public T98() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "T98 Hard Disk Image"; - public Guid Id => new Guid("0410003E-6E7B-40E6-9328-BA5651ADF6B7"); - public ImageInfo Info => imageInfo; - - public string Format => "T98 disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length % 256 != 0) return false; - - byte[] hdrB = new byte[256]; - stream.Read(hdrB, 0, hdrB.Length); - - for(int i = 4; i < 256; i++) - if(hdrB[i] != 0) - return false; - - int cylinders = BitConverter.ToInt32(hdrB, 0); - - DicConsole.DebugWriteLine("T98 plugin", "cylinders = {0}", cylinders); - - // This format is expanding, so length can be smaller - // Just grow it, I won't risk false positives... - return stream.Length == cylinders * 8 * 33 * 256 + 256; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length % 256 != 0) return false; - - byte[] hdrB = new byte[256]; - stream.Read(hdrB, 0, hdrB.Length); - - for(int i = 4; i < 256; i++) - if(hdrB[i] != 0) - return false; - - int cylinders = BitConverter.ToInt32(hdrB, 0); - - imageInfo.MediaType = MediaType.GENERIC_HDD; - - imageInfo.ImageSize = (ulong)(stream.Length - 256); - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = (ulong)(stream.Length / 256 - 1); - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.SectorSize = 256; - imageInfo.Cylinders = (uint)cylinders; - imageInfo.Heads = 8; - imageInfo.SectorsPerTrack = 33; - - t98ImageFilter = imageFilter; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Stream stream = t98ImageFilter.GetDataForkStream(); - - stream.Seek((long)(256 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - - stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); - - return buffer; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".t98"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 256) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(256 + sectorAddress * 256), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(256 + sectorAddress * 256), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - int cylinders = (int)(imageInfo.Sectors / 33 / 8); - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(BitConverter.GetBytes(cylinders), 0, 4); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Format uses fixed geometry for heads and sectors per track, cylinders must be calculated on closing. - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/T98/Identify.cs b/DiscImageChef.DiscImages/T98/Identify.cs new file mode 100644 index 000000000..bdb907d97 --- /dev/null +++ b/DiscImageChef.DiscImages/T98/Identify.cs @@ -0,0 +1,65 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies T98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class T98 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length % 256 != 0) return false; + + byte[] hdrB = new byte[256]; + stream.Read(hdrB, 0, hdrB.Length); + + for(int i = 4; i < 256; i++) + if(hdrB[i] != 0) + return false; + + int cylinders = BitConverter.ToInt32(hdrB, 0); + + DicConsole.DebugWriteLine("T98 plugin", "cylinders = {0}", cylinders); + + // This format is expanding, so length can be smaller + // Just grow it, I won't risk false positives... + return stream.Length == cylinders * 8 * 33 * 256 + 256; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/T98/Properties.cs b/DiscImageChef.DiscImages/T98/Properties.cs new file mode 100644 index 000000000..b2ddc45be --- /dev/null +++ b/DiscImageChef.DiscImages/T98/Properties.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for T98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class T98 + { + public string Name => "T98 Hard Disk Image"; + public Guid Id => new Guid("0410003E-6E7B-40E6-9328-BA5651ADF6B7"); + public ImageInfo Info => imageInfo; + + public string Format => "T98 disk image"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".t98"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/T98/Read.cs b/DiscImageChef.DiscImages/T98/Read.cs new file mode 100644 index 000000000..3e880ecb8 --- /dev/null +++ b/DiscImageChef.DiscImages/T98/Read.cs @@ -0,0 +1,101 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads T98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class T98 + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length % 256 != 0) return false; + + byte[] hdrB = new byte[256]; + stream.Read(hdrB, 0, hdrB.Length); + + for(int i = 4; i < 256; i++) + if(hdrB[i] != 0) + return false; + + int cylinders = BitConverter.ToInt32(hdrB, 0); + + imageInfo.MediaType = MediaType.GENERIC_HDD; + + imageInfo.ImageSize = (ulong)(stream.Length - 256); + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = (ulong)(stream.Length / 256 - 1); + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.SectorSize = 256; + imageInfo.Cylinders = (uint)cylinders; + imageInfo.Heads = 8; + imageInfo.SectorsPerTrack = 33; + + t98ImageFilter = imageFilter; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Stream stream = t98ImageFilter.GetDataForkStream(); + + stream.Seek((long)(256 + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + + stream.Read(buffer, 0, (int)(length * imageInfo.SectorSize)); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/T98/T98.cs b/DiscImageChef.DiscImages/T98/T98.cs new file mode 100644 index 000000000..810ee5ddc --- /dev/null +++ b/DiscImageChef.DiscImages/T98/T98.cs @@ -0,0 +1,74 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : T98.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages T98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class T98 : IWritableImage + { + ImageInfo imageInfo; + IFilter t98ImageFilter; + FileStream writingStream; + + public T98() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/T98/Unsupported.cs b/DiscImageChef.DiscImages/T98/Unsupported.cs new file mode 100644 index 000000000..6249c1889 --- /dev/null +++ b/DiscImageChef.DiscImages/T98/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by T98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class T98 + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/T98/Write.cs b/DiscImageChef.DiscImages/T98/Write.cs new file mode 100644 index 000000000..4328ebd62 --- /dev/null +++ b/DiscImageChef.DiscImages/T98/Write.cs @@ -0,0 +1,208 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes T98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class T98 + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 256) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(256 + sectorAddress * 256), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(256 + sectorAddress * 256), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + int cylinders = (int)(imageInfo.Sectors / 33 / 8); + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(BitConverter.GetBytes(cylinders), 0, 4); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Format uses fixed geometry for heads and sectors per track, cylinders must be calculated on closing. + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk/Constants.cs b/DiscImageChef.DiscImages/TeleDisk/Constants.cs new file mode 100644 index 000000000..c5fa80586 --- /dev/null +++ b/DiscImageChef.DiscImages/TeleDisk/Constants.cs @@ -0,0 +1,99 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Sydex TeleDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class TeleDisk + { + // "TD" as little endian uint. + const ushort TD_MAGIC = 0x4454; + // "td" as little endian uint. Means whole file is compressed (aka Advanced Compression) + const ushort TD_ADV_COMP_MAGIC = 0x6474; + + // DataRates + const byte DATA_RATE_250KBPS = 0x00; + const byte DATA_RATE_300KBPS = 0x01; + const byte DATA_RATE_500KBPS = 0x02; + + // TeleDisk drive types + const byte DRIVE_TYPE_525_HD_DD_DISK = 0x00; + const byte DRIVE_TYPE_525_HD = 0x01; + const byte DRIVE_TYPE_525_DD = 0x02; + const byte DRIVE_TYPE_35_DD = 0x03; + const byte DRIVE_TYPE_35_HD = 0x04; + const byte DRIVE_TYPE_8_INCH = 0x05; + const byte DRIVE_TYPE_35_ED = 0x06; + + // Stepping + const byte STEPPING_SINGLE = 0x00; + const byte STEPPING_DOUBLE = 0x01; + const byte STEPPING_EVEN_ONLY = 0x02; + // If this bit is set, there is a comment block + const byte COMMENT_BLOCK_PRESENT = 0x80; + + // CRC polynomial + const ushort TELE_DISK_CRC_POLY = 0xA097; + + // Sector sizes table + const byte SECTOR_SIZE_128 = 0x00; + const byte SECTOR_SIZE_256 = 0x01; + const byte SECTOR_SIZE_512 = 0x02; + const byte SECTOR_SIZE_1K = 0x03; + const byte SECTOR_SIZE_2K = 0x04; + const byte SECTOR_SIZE_4K = 0x05; + const byte SECTOR_SIZE_8K = 0x06; + + // Flags + // Address mark repeats inside same track + const byte FLAGS_SECTOR_DUPLICATE = 0x01; + // Sector gave CRC error on reading + const byte FLAGS_SECTOR_CRC_ERROR = 0x02; + // Address mark indicates deleted sector + const byte FLAGS_SECTOR_DELETED = 0x04; + // Sector skipped as FAT said it's unused + const byte FLAGS_SECTOR_SKIPPED = 0x10; + // There was an address mark, but no data following + const byte FLAGS_SECTOR_DATALESS = 0x20; + // There was data without address mark + const byte FLAGS_SECTOR_NO_ID = 0x40; + + // Data block encodings + // Data is copied as is + const byte DATA_BLOCK_COPY = 0x00; + // Data is encoded as a pair of len.value uint16s + const byte DATA_BLOCK_PATTERN = 0x01; + // Data is encoded as RLE + const byte DATA_BLOCK_RLE = 0x02; + + const int BUFSZ = 512; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk/Helpers.cs b/DiscImageChef.DiscImages/TeleDisk/Helpers.cs new file mode 100644 index 000000000..f8346c8f5 --- /dev/null +++ b/DiscImageChef.DiscImages/TeleDisk/Helpers.cs @@ -0,0 +1,313 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Sydex TeleDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class TeleDisk + { + (ushort cylinder, byte head, byte sector) LbaToChs(ulong lba) + { + ushort cylinder = (ushort)(lba / (imageInfo.Heads * imageInfo.SectorsPerTrack)); + byte head = (byte)(lba / imageInfo.SectorsPerTrack % imageInfo.Heads); + byte sector = (byte)(lba % imageInfo.SectorsPerTrack + 1); + + return (cylinder, head, sector); + } + + static ushort TeleDiskCrc(ushort crc, byte[] buffer) + { + int counter = 0; + + while(counter < buffer.Length) + { + crc ^= (ushort)((buffer[counter] & 0xFF) << 8); + + for(int i = 0; i < 8; i++) + if((crc & 0x8000) > 0) + crc = (ushort)((crc << 1) ^ TELE_DISK_CRC_POLY); + else + crc = (ushort)(crc << 1); + + counter++; + } + + return crc; + } + + static byte[] DecodeTeleDiskData(byte sectorSize, byte encodingType, byte[] encodedData) + { + byte[] decodedData; + switch(sectorSize) + { + case SECTOR_SIZE_128: + decodedData = new byte[128]; + break; + case SECTOR_SIZE_256: + decodedData = new byte[256]; + break; + case SECTOR_SIZE_512: + decodedData = new byte[512]; + break; + case SECTOR_SIZE_1K: + decodedData = new byte[1024]; + break; + case SECTOR_SIZE_2K: + decodedData = new byte[2048]; + break; + case SECTOR_SIZE_4K: + decodedData = new byte[4096]; + break; + case SECTOR_SIZE_8K: + decodedData = new byte[8192]; + break; + default: throw new ImageNotSupportedException($"Sector size {sectorSize} is incorrect."); + } + + switch(encodingType) + { + case DATA_BLOCK_COPY: + Array.Copy(encodedData, decodedData, decodedData.Length); + break; + case DATA_BLOCK_PATTERN: + { + int ins = 0; + int outs = 0; + while(ins < encodedData.Length) + { + byte[] repeatValue = new byte[2]; + + ushort repeatNumber = BitConverter.ToUInt16(encodedData, ins); + Array.Copy(encodedData, ins + 2, repeatValue, 0, 2); + byte[] decodedPiece = new byte[repeatNumber * 2]; + ArrayHelpers.ArrayFill(decodedPiece, repeatValue); + Array.Copy(decodedPiece, 0, decodedData, outs, decodedPiece.Length); + ins += 4; + outs += decodedPiece.Length; + } + + DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Input data size: {0} bytes", + encodedData.Length); + DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Processed input: {0} bytes", + ins); + DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Output data size: {0} bytes", + decodedData.Length); + DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Processed Output: {0} bytes", + outs); + break; + } + case DATA_BLOCK_RLE: + { + int ins = 0; + int outs = 0; + while(ins < encodedData.Length) + { + byte length; + + byte encoding = encodedData[ins]; + if(encoding == 0x00) + { + length = encodedData[ins + 1]; + Array.Copy(encodedData, ins + 2, decodedData, outs, length); + ins += 2 + length; + outs += length; + } + else + { + length = (byte)(encoding * 2); + byte run = encodedData[ins + 1]; + byte[] part = new byte[length]; + Array.Copy(encodedData, ins + 2, part, 0, length); + byte[] piece = new byte[length * run]; + ArrayHelpers.ArrayFill(piece, part); + Array.Copy(piece, 0, decodedData, outs, piece.Length); + ins += 2 + length; + outs += piece.Length; + } + } + + DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Input data size: {0} bytes", + encodedData.Length); + DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Processed input: {0} bytes", ins); + DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Output data size: {0} bytes", + decodedData.Length); + DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Processed Output: {0} bytes", outs); + + break; + } + default: throw new ImageNotSupportedException($"Data encoding {encodingType} is incorrect."); + } + + return decodedData; + } + + MediaType DecodeTeleDiskDiskType() + { + switch(header.DriveType) + { + case DRIVE_TYPE_525_DD: + case DRIVE_TYPE_525_HD_DD_DISK: + case DRIVE_TYPE_525_HD: + { + switch(totalDiskSize) + { + case 163840: + { + // Acorn disk uses 256 bytes/sector + return imageInfo.SectorSize == 256 + ? MediaType.ACORN_525_SS_DD_40 + : MediaType.DOS_525_SS_DD_8; + + // DOS disks use 512 bytes/sector + } + case 184320: + { + // Atari disk uses 256 bytes/sector + return imageInfo.SectorSize == 256 ? MediaType.ATARI_525_DD : MediaType.DOS_525_SS_DD_9; + + // DOS disks use 512 bytes/sector + } + case 327680: + { + // Acorn disk uses 256 bytes/sector + return imageInfo.SectorSize == 256 + ? MediaType.ACORN_525_SS_DD_80 + : MediaType.DOS_525_DS_DD_8; + + // DOS disks use 512 bytes/sector + } + case 368640: return MediaType.DOS_525_DS_DD_9; + case 1228800: return MediaType.DOS_525_HD; + case 102400: return MediaType.ACORN_525_SS_SD_40; + case 204800: return MediaType.ACORN_525_SS_SD_80; + case 655360: return MediaType.ACORN_525_DS_DD; + case 92160: return MediaType.ATARI_525_SD; + case 133120: return MediaType.ATARI_525_ED; + case 1310720: return MediaType.NEC_525_HD; + case 1261568: return MediaType.SHARP_525; + case 839680: return MediaType.FDFORMAT_525_DD; + case 1304320: return MediaType.ECMA_99_8; + case 1223424: return MediaType.ECMA_99_15; + case 1061632: return MediaType.ECMA_99_26; + case 80384: return MediaType.ECMA_66; + case 325632: return MediaType.ECMA_70; + case 653312: return MediaType.ECMA_78; + case 737280: return MediaType.ECMA_78_2; + default: + { + DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 5,25\" disk with {0} bytes", + totalDiskSize); + return MediaType.Unknown; + } + } + } + case DRIVE_TYPE_35_DD: + case DRIVE_TYPE_35_ED: + case DRIVE_TYPE_35_HD: + { + switch(totalDiskSize) + { + case 322560: return MediaType.Apricot_35; + case 327680: return MediaType.DOS_35_SS_DD_8; + case 368640: return MediaType.DOS_35_SS_DD_9; + case 655360: return MediaType.DOS_35_DS_DD_8; + case 737280: return MediaType.DOS_35_DS_DD_9; + case 1474560: return MediaType.DOS_35_HD; + case 2949120: return MediaType.DOS_35_ED; + case 1720320: return MediaType.DMF; + case 1763328: return MediaType.DMF_82; + case 1884160: // Irreal size, seen as BIOS with TSR, 23 sectors/track + case 1860608: // Real data size, sum of all sectors + return MediaType.XDF_35; + case 819200: return MediaType.CBM_35_DD; + case 901120: return MediaType.CBM_AMIGA_35_DD; + case 1802240: return MediaType.CBM_AMIGA_35_HD; + case 1310720: return MediaType.NEC_35_HD_8; + case 1228800: return MediaType.NEC_35_HD_15; + case 1261568: return MediaType.SHARP_35; + default: + { + DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 3,5\" disk with {0} bytes", + totalDiskSize); + return MediaType.Unknown; + } + } + } + case DRIVE_TYPE_8_INCH: + { + switch(totalDiskSize) + { + case 81664: return MediaType.IBM23FD; + case 242944: return MediaType.IBM33FD_128; + case 287488: return MediaType.IBM33FD_256; + case 306432: return MediaType.IBM33FD_512; + case 499200: return MediaType.IBM43FD_128; + case 574976: return MediaType.IBM43FD_256; + case 995072: return MediaType.IBM53FD_256; + case 1146624: return MediaType.IBM53FD_512; + case 1222400: return MediaType.IBM53FD_1024; + case 256256: + // Same size, with same disk geometry, for DEC RX01, NEC and ECMA, return ECMA + return MediaType.ECMA_54; + case 512512: + { + // DEC disk uses 256 bytes/sector + return imageInfo.SectorSize == 256 ? MediaType.RX02 : MediaType.ECMA_59; + + // ECMA disks use 128 bytes/sector + } + case 1261568: return MediaType.NEC_8_DD; + case 1255168: return MediaType.ECMA_69_8; + case 1177344: return MediaType.ECMA_69_15; + case 1021696: return MediaType.ECMA_69_26; + default: + { + DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 8\" disk with {0} bytes", + totalDiskSize); + return MediaType.Unknown; + } + } + } + default: + { + DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown drive type {1} with {0} bytes", totalDiskSize, + header.DriveType); + return MediaType.Unknown; + } + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk/Identify.cs b/DiscImageChef.DiscImages/TeleDisk/Identify.cs new file mode 100644 index 000000000..5df1c2c34 --- /dev/null +++ b/DiscImageChef.DiscImages/TeleDisk/Identify.cs @@ -0,0 +1,98 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Sydex TeleDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class TeleDisk + { + public bool Identify(IFilter imageFilter) + { + header = new TeleDiskHeader(); + byte[] headerBytes = new byte[12]; + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + stream.Read(headerBytes, 0, 12); + + header.Signature = BitConverter.ToUInt16(headerBytes, 0); + + if(header.Signature != TD_MAGIC && header.Signature != TD_ADV_COMP_MAGIC) return false; + + header.Sequence = headerBytes[2]; + header.DiskSet = headerBytes[3]; + header.Version = headerBytes[4]; + header.DataRate = headerBytes[5]; + header.DriveType = headerBytes[6]; + header.Stepping = headerBytes[7]; + header.DosAllocation = headerBytes[8]; + header.Sides = headerBytes[9]; + header.Crc = BitConverter.ToUInt16(headerBytes, 10); + + byte[] headerBytesForCrc = new byte[10]; + Array.Copy(headerBytes, headerBytesForCrc, 10); + ushort calculatedHeaderCrc = TeleDiskCrc(0x0000, headerBytesForCrc); + + DicConsole.DebugWriteLine("TeleDisk plugin", "header.signature = 0x{0:X4}", header.Signature); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.sequence = 0x{0:X2}", header.Sequence); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.diskSet = 0x{0:X2}", header.DiskSet); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.version = 0x{0:X2}", header.Version); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.dataRate = 0x{0:X2}", header.DataRate); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.driveType = 0x{0:X2}", header.DriveType); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.stepping = 0x{0:X2}", header.Stepping); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.dosAllocation = 0x{0:X2}", header.DosAllocation); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.sides = 0x{0:X2}", header.Sides); + DicConsole.DebugWriteLine("TeleDisk plugin", "header.crc = 0x{0:X4}", header.Crc); + DicConsole.DebugWriteLine("TeleDisk plugin", "calculated header crc = 0x{0:X4}", calculatedHeaderCrc); + + // We need more checks as the magic is too simply. + // This may deny legal images + + // That would be much of a coincidence + if(header.Crc == calculatedHeaderCrc) return true; + + if(header.Sequence != 0x00) return false; + + if(header.DataRate != DATA_RATE_250KBPS && header.DataRate != DATA_RATE_300KBPS && + header.DataRate != DATA_RATE_500KBPS) return false; + + return header.DriveType == DRIVE_TYPE_35_DD || header.DriveType == DRIVE_TYPE_35_ED || + header.DriveType == DRIVE_TYPE_35_HD || header.DriveType == DRIVE_TYPE_525_DD || + header.DriveType == DRIVE_TYPE_525_HD || header.DriveType == DRIVE_TYPE_525_HD_DD_DISK || + header.DriveType == DRIVE_TYPE_8_INCH; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk/Properties.cs b/DiscImageChef.DiscImages/TeleDisk/Properties.cs new file mode 100644 index 000000000..38b5275af --- /dev/null +++ b/DiscImageChef.DiscImages/TeleDisk/Properties.cs @@ -0,0 +1,63 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Sydex TeleDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class TeleDisk + { + public ImageInfo Info => imageInfo; + + public string Name => "Sydex TeleDisk"; + public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88"); + + public string Format => "Sydex TeleDisk"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk.cs b/DiscImageChef.DiscImages/TeleDisk/Read.cs similarity index 52% rename from DiscImageChef.DiscImages/TeleDisk.cs rename to DiscImageChef.DiscImages/TeleDisk/Read.cs index e8da73657..871540239 100644 --- a/DiscImageChef.DiscImages/TeleDisk.cs +++ b/DiscImageChef.DiscImages/TeleDisk/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : TeleDisk.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Sydex TeleDisk disk images. +// Reads Sydex TeleDisk disk images. // // --[ License ] -------------------------------------------------------------- // @@ -34,195 +34,16 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; -using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Compression; using DiscImageChef.Console; -using Schemas; namespace DiscImageChef.DiscImages { - // Created following notes from Dave Dunfield - // http://www.classiccmp.org/dunfield/img54306/td0notes.txt - public class TeleDisk : IMediaImage + public partial class TeleDisk { - // "TD" as little endian uint. - const ushort TD_MAGIC = 0x4454; - // "td" as little endian uint. Means whole file is compressed (aka Advanced Compression) - const ushort TD_ADV_COMP_MAGIC = 0x6474; - - // DataRates - const byte DATA_RATE_250KBPS = 0x00; - const byte DATA_RATE_300KBPS = 0x01; - const byte DATA_RATE_500KBPS = 0x02; - - // TeleDisk drive types - const byte DRIVE_TYPE_525_HD_DD_DISK = 0x00; - const byte DRIVE_TYPE_525_HD = 0x01; - const byte DRIVE_TYPE_525_DD = 0x02; - const byte DRIVE_TYPE_35_DD = 0x03; - const byte DRIVE_TYPE_35_HD = 0x04; - const byte DRIVE_TYPE_8_INCH = 0x05; - const byte DRIVE_TYPE_35_ED = 0x06; - - // Stepping - const byte STEPPING_SINGLE = 0x00; - const byte STEPPING_DOUBLE = 0x01; - const byte STEPPING_EVEN_ONLY = 0x02; - // If this bit is set, there is a comment block - const byte COMMENT_BLOCK_PRESENT = 0x80; - - // CRC polynomial - const ushort TELE_DISK_CRC_POLY = 0xA097; - - // Sector sizes table - const byte SECTOR_SIZE_128 = 0x00; - const byte SECTOR_SIZE_256 = 0x01; - const byte SECTOR_SIZE_512 = 0x02; - const byte SECTOR_SIZE_1K = 0x03; - const byte SECTOR_SIZE_2K = 0x04; - const byte SECTOR_SIZE_4K = 0x05; - const byte SECTOR_SIZE_8K = 0x06; - - // Flags - // Address mark repeats inside same track - const byte FLAGS_SECTOR_DUPLICATE = 0x01; - // Sector gave CRC error on reading - const byte FLAGS_SECTOR_CRC_ERROR = 0x02; - // Address mark indicates deleted sector - const byte FLAGS_SECTOR_DELETED = 0x04; - // Sector skipped as FAT said it's unused - const byte FLAGS_SECTOR_SKIPPED = 0x10; - // There was an address mark, but no data following - const byte FLAGS_SECTOR_DATALESS = 0x20; - // There was data without address mark - const byte FLAGS_SECTOR_NO_ID = 0x40; - - // Data block encodings - // Data is copied as is - const byte DATA_BLOCK_COPY = 0x00; - // Data is encoded as a pair of len.value uint16s - const byte DATA_BLOCK_PATTERN = 0x01; - // Data is encoded as RLE - const byte DATA_BLOCK_RLE = 0x02; - - const int BUFSZ = 512; - bool aDiskCrcHasFailed; - byte[] commentBlock; - TeleDiskCommentBlockHeader commentHeader; - - TeleDiskHeader header; - ImageInfo imageInfo; - Stream inStream; - byte[] leadOut; - // Cylinder by head, sector data matrix - byte[][][][] sectorsData; - List<ulong> sectorsWhereCrcHasFailed; - // LBA, data - uint totalDiskSize; - - public TeleDisk() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Application = "Sydex TeleDisk", - Comments = null, - Creator = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - aDiskCrcHasFailed = false; - sectorsWhereCrcHasFailed = new List<ulong>(); - } - - public ImageInfo Info => imageInfo; - - public string Name => "Sydex TeleDisk"; - public Guid Id => new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88"); - - public string Format => "Sydex TeleDisk"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - header = new TeleDiskHeader(); - byte[] headerBytes = new byte[12]; - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - stream.Read(headerBytes, 0, 12); - - header.Signature = BitConverter.ToUInt16(headerBytes, 0); - - if(header.Signature != TD_MAGIC && header.Signature != TD_ADV_COMP_MAGIC) return false; - - header.Sequence = headerBytes[2]; - header.DiskSet = headerBytes[3]; - header.Version = headerBytes[4]; - header.DataRate = headerBytes[5]; - header.DriveType = headerBytes[6]; - header.Stepping = headerBytes[7]; - header.DosAllocation = headerBytes[8]; - header.Sides = headerBytes[9]; - header.Crc = BitConverter.ToUInt16(headerBytes, 10); - - byte[] headerBytesForCrc = new byte[10]; - Array.Copy(headerBytes, headerBytesForCrc, 10); - ushort calculatedHeaderCrc = TeleDiskCrc(0x0000, headerBytesForCrc); - - DicConsole.DebugWriteLine("TeleDisk plugin", "header.signature = 0x{0:X4}", header.Signature); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.sequence = 0x{0:X2}", header.Sequence); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.diskSet = 0x{0:X2}", header.DiskSet); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.version = 0x{0:X2}", header.Version); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.dataRate = 0x{0:X2}", header.DataRate); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.driveType = 0x{0:X2}", header.DriveType); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.stepping = 0x{0:X2}", header.Stepping); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.dosAllocation = 0x{0:X2}", header.DosAllocation); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.sides = 0x{0:X2}", header.Sides); - DicConsole.DebugWriteLine("TeleDisk plugin", "header.crc = 0x{0:X4}", header.Crc); - DicConsole.DebugWriteLine("TeleDisk plugin", "calculated header crc = 0x{0:X4}", calculatedHeaderCrc); - - // We need more checks as the magic is too simply. - // This may deny legal images - - // That would be much of a coincidence - if(header.Crc == calculatedHeaderCrc) return true; - - if(header.Sequence != 0x00) return false; - - if(header.DataRate != DATA_RATE_250KBPS && header.DataRate != DATA_RATE_300KBPS && - header.DataRate != DATA_RATE_500KBPS) return false; - - return header.DriveType == DRIVE_TYPE_35_DD || header.DriveType == DRIVE_TYPE_35_ED || - header.DriveType == DRIVE_TYPE_35_HD || header.DriveType == DRIVE_TYPE_525_DD || - header.DriveType == DRIVE_TYPE_525_HD || header.DriveType == DRIVE_TYPE_525_HD_DD_DISK || - header.DriveType == DRIVE_TYPE_8_INCH; - } - public bool Open(IFilter imageFilter) { header = new TeleDiskHeader(); @@ -747,9 +568,6 @@ namespace DiscImageChef.DiscImages return aDiskCrcHasFailed; } - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - public byte[] ReadDiskTag(MediaTagType tag) { if(tag != MediaTagType.Floppy_LeadOut) @@ -759,400 +577,5 @@ namespace DiscImageChef.DiscImages throw new FeatureNotPresentImageException("Lead-out not present in disk image"); } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - (ushort cylinder, byte head, byte sector) LbaToChs(ulong lba) - { - ushort cylinder = (ushort)(lba / (imageInfo.Heads * imageInfo.SectorsPerTrack)); - byte head = (byte)(lba / imageInfo.SectorsPerTrack % imageInfo.Heads); - byte sector = (byte)(lba % imageInfo.SectorsPerTrack + 1); - - return (cylinder, head, sector); - } - - static ushort TeleDiskCrc(ushort crc, byte[] buffer) - { - int counter = 0; - - while(counter < buffer.Length) - { - crc ^= (ushort)((buffer[counter] & 0xFF) << 8); - - for(int i = 0; i < 8; i++) - if((crc & 0x8000) > 0) - crc = (ushort)((crc << 1) ^ TELE_DISK_CRC_POLY); - else - crc = (ushort)(crc << 1); - - counter++; - } - - return crc; - } - - static byte[] DecodeTeleDiskData(byte sectorSize, byte encodingType, byte[] encodedData) - { - byte[] decodedData; - switch(sectorSize) - { - case SECTOR_SIZE_128: - decodedData = new byte[128]; - break; - case SECTOR_SIZE_256: - decodedData = new byte[256]; - break; - case SECTOR_SIZE_512: - decodedData = new byte[512]; - break; - case SECTOR_SIZE_1K: - decodedData = new byte[1024]; - break; - case SECTOR_SIZE_2K: - decodedData = new byte[2048]; - break; - case SECTOR_SIZE_4K: - decodedData = new byte[4096]; - break; - case SECTOR_SIZE_8K: - decodedData = new byte[8192]; - break; - default: throw new ImageNotSupportedException($"Sector size {sectorSize} is incorrect."); - } - - switch(encodingType) - { - case DATA_BLOCK_COPY: - Array.Copy(encodedData, decodedData, decodedData.Length); - break; - case DATA_BLOCK_PATTERN: - { - int ins = 0; - int outs = 0; - while(ins < encodedData.Length) - { - byte[] repeatValue = new byte[2]; - - ushort repeatNumber = BitConverter.ToUInt16(encodedData, ins); - Array.Copy(encodedData, ins + 2, repeatValue, 0, 2); - byte[] decodedPiece = new byte[repeatNumber * 2]; - ArrayHelpers.ArrayFill(decodedPiece, repeatValue); - Array.Copy(decodedPiece, 0, decodedData, outs, decodedPiece.Length); - ins += 4; - outs += decodedPiece.Length; - } - - DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Input data size: {0} bytes", - encodedData.Length); - DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Processed input: {0} bytes", - ins); - DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Output data size: {0} bytes", - decodedData.Length); - DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Processed Output: {0} bytes", - outs); - break; - } - case DATA_BLOCK_RLE: - { - int ins = 0; - int outs = 0; - while(ins < encodedData.Length) - { - byte length; - - byte encoding = encodedData[ins]; - if(encoding == 0x00) - { - length = encodedData[ins + 1]; - Array.Copy(encodedData, ins + 2, decodedData, outs, length); - ins += 2 + length; - outs += length; - } - else - { - length = (byte)(encoding * 2); - byte run = encodedData[ins + 1]; - byte[] part = new byte[length]; - Array.Copy(encodedData, ins + 2, part, 0, length); - byte[] piece = new byte[length * run]; - ArrayHelpers.ArrayFill(piece, part); - Array.Copy(piece, 0, decodedData, outs, piece.Length); - ins += 2 + length; - outs += piece.Length; - } - } - - DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Input data size: {0} bytes", - encodedData.Length); - DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Processed input: {0} bytes", ins); - DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Output data size: {0} bytes", - decodedData.Length); - DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Processed Output: {0} bytes", outs); - - break; - } - default: throw new ImageNotSupportedException($"Data encoding {encodingType} is incorrect."); - } - - return decodedData; - } - - MediaType DecodeTeleDiskDiskType() - { - switch(header.DriveType) - { - case DRIVE_TYPE_525_DD: - case DRIVE_TYPE_525_HD_DD_DISK: - case DRIVE_TYPE_525_HD: - { - switch(totalDiskSize) - { - case 163840: - { - // Acorn disk uses 256 bytes/sector - return imageInfo.SectorSize == 256 - ? MediaType.ACORN_525_SS_DD_40 - : MediaType.DOS_525_SS_DD_8; - - // DOS disks use 512 bytes/sector - } - case 184320: - { - // Atari disk uses 256 bytes/sector - return imageInfo.SectorSize == 256 ? MediaType.ATARI_525_DD : MediaType.DOS_525_SS_DD_9; - - // DOS disks use 512 bytes/sector - } - case 327680: - { - // Acorn disk uses 256 bytes/sector - return imageInfo.SectorSize == 256 - ? MediaType.ACORN_525_SS_DD_80 - : MediaType.DOS_525_DS_DD_8; - - // DOS disks use 512 bytes/sector - } - case 368640: return MediaType.DOS_525_DS_DD_9; - case 1228800: return MediaType.DOS_525_HD; - case 102400: return MediaType.ACORN_525_SS_SD_40; - case 204800: return MediaType.ACORN_525_SS_SD_80; - case 655360: return MediaType.ACORN_525_DS_DD; - case 92160: return MediaType.ATARI_525_SD; - case 133120: return MediaType.ATARI_525_ED; - case 1310720: return MediaType.NEC_525_HD; - case 1261568: return MediaType.SHARP_525; - case 839680: return MediaType.FDFORMAT_525_DD; - case 1304320: return MediaType.ECMA_99_8; - case 1223424: return MediaType.ECMA_99_15; - case 1061632: return MediaType.ECMA_99_26; - case 80384: return MediaType.ECMA_66; - case 325632: return MediaType.ECMA_70; - case 653312: return MediaType.ECMA_78; - case 737280: return MediaType.ECMA_78_2; - default: - { - DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 5,25\" disk with {0} bytes", - totalDiskSize); - return MediaType.Unknown; - } - } - } - case DRIVE_TYPE_35_DD: - case DRIVE_TYPE_35_ED: - case DRIVE_TYPE_35_HD: - { - switch(totalDiskSize) - { - case 322560: return MediaType.Apricot_35; - case 327680: return MediaType.DOS_35_SS_DD_8; - case 368640: return MediaType.DOS_35_SS_DD_9; - case 655360: return MediaType.DOS_35_DS_DD_8; - case 737280: return MediaType.DOS_35_DS_DD_9; - case 1474560: return MediaType.DOS_35_HD; - case 2949120: return MediaType.DOS_35_ED; - case 1720320: return MediaType.DMF; - case 1763328: return MediaType.DMF_82; - case 1884160: // Irreal size, seen as BIOS with TSR, 23 sectors/track - case 1860608: // Real data size, sum of all sectors - return MediaType.XDF_35; - case 819200: return MediaType.CBM_35_DD; - case 901120: return MediaType.CBM_AMIGA_35_DD; - case 1802240: return MediaType.CBM_AMIGA_35_HD; - case 1310720: return MediaType.NEC_35_HD_8; - case 1228800: return MediaType.NEC_35_HD_15; - case 1261568: return MediaType.SHARP_35; - default: - { - DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 3,5\" disk with {0} bytes", - totalDiskSize); - return MediaType.Unknown; - } - } - } - case DRIVE_TYPE_8_INCH: - { - switch(totalDiskSize) - { - case 81664: return MediaType.IBM23FD; - case 242944: return MediaType.IBM33FD_128; - case 287488: return MediaType.IBM33FD_256; - case 306432: return MediaType.IBM33FD_512; - case 499200: return MediaType.IBM43FD_128; - case 574976: return MediaType.IBM43FD_256; - case 995072: return MediaType.IBM53FD_256; - case 1146624: return MediaType.IBM53FD_512; - case 1222400: return MediaType.IBM53FD_1024; - case 256256: - // Same size, with same disk geometry, for DEC RX01, NEC and ECMA, return ECMA - return MediaType.ECMA_54; - case 512512: - { - // DEC disk uses 256 bytes/sector - return imageInfo.SectorSize == 256 ? MediaType.RX02 : MediaType.ECMA_59; - - // ECMA disks use 128 bytes/sector - } - case 1261568: return MediaType.NEC_8_DD; - case 1255168: return MediaType.ECMA_69_8; - case 1177344: return MediaType.ECMA_69_15; - case 1021696: return MediaType.ECMA_69_26; - default: - { - DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 8\" disk with {0} bytes", - totalDiskSize); - return MediaType.Unknown; - } - } - } - default: - { - DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown drive type {1} with {0} bytes", totalDiskSize, - header.DriveType); - return MediaType.Unknown; - } - } - } - - struct TeleDiskHeader - { - /// <summary>"TD" or "td" depending on compression</summary> - public ushort Signature; - /// <summary>Sequence, but TeleDisk seems to complaing if != 0</summary> - public byte Sequence; - /// <summary>Random, same byte for all disks in the same set</summary> - public byte DiskSet; - /// <summary>TeleDisk version, major in high nibble, minor in low nibble</summary> - public byte Version; - /// <summary>Data rate</summary> - public byte DataRate; - /// <summary>BIOS drive type</summary> - public byte DriveType; - /// <summary>Stepping used</summary> - public byte Stepping; - /// <summary>If set means image only allocates sectors marked in-use by FAT12</summary> - public byte DosAllocation; - /// <summary>Sides of disk</summary> - public byte Sides; - /// <summary>CRC of all the previous</summary> - public ushort Crc; - } - - struct TeleDiskCommentBlockHeader - { - /// <summary>CRC of comment block after crc field</summary> - public ushort Crc; - /// <summary>Length of comment</summary> - public ushort Length; - public byte Year; - public byte Month; - public byte Day; - public byte Hour; - public byte Minute; - public byte Second; - } - - struct TeleDiskTrackHeader - { - /// <summary>Sectors in the track, 0xFF if end of disk image (there is no spoon)</summary> - public byte Sectors; - /// <summary>Cylinder the head was on</summary> - public byte Cylinder; - /// <summary>Head/side used</summary> - public byte Head; - /// <summary>Lower byte of CRC of previous fields</summary> - public byte Crc; - } - - struct TeleDiskSectorHeader - { - /// <summary>Cylinder as stored on sector address mark</summary> - public byte Cylinder; - /// <summary>Head as stored on sector address mark</summary> - public byte Head; - /// <summary>Sector number as stored on sector address mark</summary> - public byte SectorNumber; - /// <summary>Sector size</summary> - public byte SectorSize; - /// <summary>Sector flags</summary> - public byte Flags; - /// <summary>Lower byte of TeleDisk CRC of sector header, data header and data block</summary> - public byte Crc; - } - - struct TeleDiskDataHeader - { - /// <summary>Size of all data (encoded) + next field (1)</summary> - public ushort DataSize; - /// <summary>Encoding used for data block</summary> - public byte DataEncoding; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk/Structs.cs b/DiscImageChef.DiscImages/TeleDisk/Structs.cs new file mode 100644 index 000000000..141ce9a80 --- /dev/null +++ b/DiscImageChef.DiscImages/TeleDisk/Structs.cs @@ -0,0 +1,111 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Sydex TeleDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class TeleDisk + { + struct TeleDiskHeader + { + /// <summary>"TD" or "td" depending on compression</summary> + public ushort Signature; + /// <summary>Sequence, but TeleDisk seems to complaing if != 0</summary> + public byte Sequence; + /// <summary>Random, same byte for all disks in the same set</summary> + public byte DiskSet; + /// <summary>TeleDisk version, major in high nibble, minor in low nibble</summary> + public byte Version; + /// <summary>Data rate</summary> + public byte DataRate; + /// <summary>BIOS drive type</summary> + public byte DriveType; + /// <summary>Stepping used</summary> + public byte Stepping; + /// <summary>If set means image only allocates sectors marked in-use by FAT12</summary> + public byte DosAllocation; + /// <summary>Sides of disk</summary> + public byte Sides; + /// <summary>CRC of all the previous</summary> + public ushort Crc; + } + + struct TeleDiskCommentBlockHeader + { + /// <summary>CRC of comment block after crc field</summary> + public ushort Crc; + /// <summary>Length of comment</summary> + public ushort Length; + public byte Year; + public byte Month; + public byte Day; + public byte Hour; + public byte Minute; + public byte Second; + } + + struct TeleDiskTrackHeader + { + /// <summary>Sectors in the track, 0xFF if end of disk image (there is no spoon)</summary> + public byte Sectors; + /// <summary>Cylinder the head was on</summary> + public byte Cylinder; + /// <summary>Head/side used</summary> + public byte Head; + /// <summary>Lower byte of CRC of previous fields</summary> + public byte Crc; + } + + struct TeleDiskSectorHeader + { + /// <summary>Cylinder as stored on sector address mark</summary> + public byte Cylinder; + /// <summary>Head as stored on sector address mark</summary> + public byte Head; + /// <summary>Sector number as stored on sector address mark</summary> + public byte SectorNumber; + /// <summary>Sector size</summary> + public byte SectorSize; + /// <summary>Sector flags</summary> + public byte Flags; + /// <summary>Lower byte of TeleDisk CRC of sector header, data header and data block</summary> + public byte Crc; + } + + struct TeleDiskDataHeader + { + /// <summary>Size of all data (encoded) + next field (1)</summary> + public ushort DataSize; + /// <summary>Encoding used for data block</summary> + public byte DataEncoding; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk/TeleDisk.cs b/DiscImageChef.DiscImages/TeleDisk/TeleDisk.cs new file mode 100644 index 000000000..a27f82769 --- /dev/null +++ b/DiscImageChef.DiscImages/TeleDisk/TeleDisk.cs @@ -0,0 +1,85 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : TeleDisk.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Sydex TeleDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // Created following notes from Dave Dunfield + // http://www.classiccmp.org/dunfield/img54306/td0notes.txt + public partial class TeleDisk : IMediaImage + { + bool aDiskCrcHasFailed; + byte[] commentBlock; + TeleDiskCommentBlockHeader commentHeader; + TeleDiskHeader header; + ImageInfo imageInfo; + Stream inStream; + byte[] leadOut; + // Cylinder by head, sector data matrix + byte[][][][] sectorsData; + List<ulong> sectorsWhereCrcHasFailed; + // LBA, data + uint totalDiskSize; + + public TeleDisk() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Application = "Sydex TeleDisk", + Comments = null, + Creator = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + aDiskCrcHasFailed = false; + sectorsWhereCrcHasFailed = new List<ulong>(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/TeleDisk/Unsupported.cs b/DiscImageChef.DiscImages/TeleDisk/Unsupported.cs new file mode 100644 index 000000000..7d7e6086d --- /dev/null +++ b/DiscImageChef.DiscImages/TeleDisk/Unsupported.cs @@ -0,0 +1,92 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Sydex TeleDisk disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class TeleDisk + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF.cs b/DiscImageChef.DiscImages/UDIF.cs deleted file mode 100644 index d77567b88..000000000 --- a/DiscImageChef.DiscImages/UDIF.cs +++ /dev/null @@ -1,1164 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : UDIF.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Apple Universal Disk Image Format. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using Claunia.PropertyList; -using Claunia.RsrcFork; -using DiscImageChef.Checksums; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Compression; -using DiscImageChef.Console; -using Ionic.Zlib; -using Schemas; -using SharpCompress.Compressors.ADC; -using SharpCompress.Compressors.BZip2; -using Version = Resources.Version; - -#pragma warning disable 612 - -namespace DiscImageChef.DiscImages -{ - public class Udif : IWritableImage - { - const uint UDIF_SIGNATURE = 0x6B6F6C79; - const uint CHUNK_SIGNATURE = 0x6D697368; - - // All chunk types with this mask are compressed - const uint CHUNK_TYPE_COMPRESSED_MASK = 0x80000000; - - const uint CHUNK_TYPE_ZERO = 0x00000000; - const uint CHUNK_TYPE_COPY = 0x00000001; - const uint CHUNK_TYPE_NOCOPY = 0x00000002; - const uint CHUNK_TYPE_KENCODE = 0x80000001; - const uint CHUNK_TYPE_RLE = 0x80000002; - const uint CHUNK_TYPE_LZH = 0x80000003; - const uint CHUNK_TYPE_ADC = 0x80000004; - const uint CHUNK_TYPE_ZLIB = 0x80000005; - const uint CHUNK_TYPE_BZIP = 0x80000006; - const uint CHUNK_TYPE_LZFSE = 0x80000007; - const uint CHUNK_TYPE_COMMNT = 0x7FFFFFFE; - const uint CHUNK_TYPE_END = 0xFFFFFFFF; - - const uint UDIF_CHECKSUM_TYPE_NONE = 0; - const uint UDIF_CHECKSUM_TYPE_CRC28 = 1; - const uint UDIF_CHECKSUM_TYPE_CRC32 = 2; - const uint UDIF_CHECKSUM_TYPE_DC42 = 3; - const uint UDIF_CHECKSUM_TYPE_MD5 = 4; - const uint UDIF_CHECKSUM_TYPE_SHA = 5; - const uint UDIF_CHECKSUM_TYPE_SHA1 = 6; - const uint UDIF_CHECKSUM_TYPE_SHA256 = 7; - const uint UDIF_CHECKSUM_TYPE_SHA384 = 8; - const uint UDIF_CHECKSUM_TYPE_SHA512 = 9; - - const string RESOURCE_FORK_KEY = "resource-fork"; - const string BLOCK_KEY = "blkx"; - const uint BLOCK_OS_TYPE = 0x626C6B78; - const uint MAX_CACHE_SIZE = 16777216; - const uint SECTOR_SIZE = 512; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; - uint buffersize; - Dictionary<ulong, byte[]> chunkCache; - Dictionary<ulong, BlockChunk> chunks; - BlockChunk currentChunk; - uint currentChunkCacheSize; - ulong currentSector; - Crc32Context dataForkChecksum; - - UdifFooter footer; - ImageInfo imageInfo; - - Stream imageStream; - Crc32Context masterChecksum; - - Dictionary<ulong, byte[]> sectorCache; - FileStream writingStream; - - public Udif() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Apple Universal Disk Image Format"; - public Guid Id => new Guid("5BEB9002-CF3D-429C-8E06-9A96F49203FF"); - - public string Format => "Apple Universal Disk Image Format"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if(stream.Length < 512) return false; - - stream.Seek(-Marshal.SizeOf(footer), SeekOrigin.End); - byte[] footerB = new byte[Marshal.SizeOf(footer)]; - - stream.Read(footerB, 0, Marshal.SizeOf(footer)); - footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(footerB); - - if(footer.signature == UDIF_SIGNATURE) return true; - - // Old UDIF as created by DiskCopy 6.5 using "OBSOLETE" format. (DiskCopy 5 rumored format?) - stream.Seek(0, SeekOrigin.Begin); - byte[] headerB = new byte[Marshal.SizeOf(footer)]; - - stream.Read(headerB, 0, Marshal.SizeOf(footer)); - footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(headerB); - - return footer.signature == UDIF_SIGNATURE; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - if(stream.Length < 512) return false; - - stream.Seek(-Marshal.SizeOf(footer), SeekOrigin.End); - byte[] footerB = new byte[Marshal.SizeOf(footer)]; - - stream.Read(footerB, 0, Marshal.SizeOf(footer)); - footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(footerB); - - if(footer.signature != UDIF_SIGNATURE) - { - stream.Seek(0, SeekOrigin.Begin); - footerB = new byte[Marshal.SizeOf(footer)]; - - stream.Read(footerB, 0, Marshal.SizeOf(footer)); - footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(footerB); - - if(footer.signature != UDIF_SIGNATURE) throw new Exception("Unable to find UDIF signature."); - - DicConsole.VerboseWriteLine("Found obsolete UDIF format."); - } - - DicConsole.DebugWriteLine("UDIF plugin", "footer.signature = 0x{0:X8}", footer.signature); - DicConsole.DebugWriteLine("UDIF plugin", "footer.version = {0}", footer.version); - DicConsole.DebugWriteLine("UDIF plugin", "footer.headerSize = {0}", footer.headerSize); - DicConsole.DebugWriteLine("UDIF plugin", "footer.flags = {0}", footer.flags); - DicConsole.DebugWriteLine("UDIF plugin", "footer.runningDataForkOff = {0}", footer.runningDataForkOff); - DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkOff = {0}", footer.dataForkOff); - DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkLen = {0}", footer.dataForkLen); - DicConsole.DebugWriteLine("UDIF plugin", "footer.rsrcForkOff = {0}", footer.rsrcForkOff); - DicConsole.DebugWriteLine("UDIF plugin", "footer.rsrcForkLen = {0}", footer.rsrcForkLen); - DicConsole.DebugWriteLine("UDIF plugin", "footer.segmentNumber = {0}", footer.segmentNumber); - DicConsole.DebugWriteLine("UDIF plugin", "footer.segmentCount = {0}", footer.segmentCount); - DicConsole.DebugWriteLine("UDIF plugin", "footer.segmentId = {0}", footer.segmentId); - DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkChkType = {0}", footer.dataForkChkType); - DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkLen = {0}", footer.dataForkLen); - DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkChk = 0x{0:X8}", footer.dataForkChk); - DicConsole.DebugWriteLine("UDIF plugin", "footer.plistOff = {0}", footer.plistOff); - DicConsole.DebugWriteLine("UDIF plugin", "footer.plistLen = {0}", footer.plistLen); - DicConsole.DebugWriteLine("UDIF plugin", "footer.masterChkType = {0}", footer.masterChkType); - DicConsole.DebugWriteLine("UDIF plugin", "footer.masterChkLen = {0}", footer.masterChkLen); - DicConsole.DebugWriteLine("UDIF plugin", "footer.masterChk = 0x{0:X8}", footer.masterChk); - DicConsole.DebugWriteLine("UDIF plugin", "footer.imageVariant = {0}", footer.imageVariant); - DicConsole.DebugWriteLine("UDIF plugin", "footer.sectorCount = {0}", footer.sectorCount); - DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved1 is empty? = {0}", - ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved1)); - DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved2 is empty? = {0}", - ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved2)); - DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved3 is empty? = {0}", - ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved3)); - DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved4 is empty? = {0}", - ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved4)); - - // Block chunks and headers - List<byte[]> blkxList = new List<byte[]>(); - chunks = new Dictionary<ulong, BlockChunk>(); - - bool fakeBlockChunks = false; - - byte[] vers = null; - - if(footer.plistLen == 0 && footer.rsrcForkLen != 0) - { - DicConsole.DebugWriteLine("UDIF plugin", "Reading resource fork."); - byte[] rsrcB = new byte[footer.rsrcForkLen]; - stream.Seek((long)footer.rsrcForkOff, SeekOrigin.Begin); - stream.Read(rsrcB, 0, rsrcB.Length); - - ResourceFork rsrc = new ResourceFork(rsrcB); - - if(!rsrc.ContainsKey(BLOCK_OS_TYPE)) - throw new - ImageNotSupportedException("Image resource fork doesn't contain UDIF block chunks. Please fill an issue and send it to us."); - - Resource blkxRez = rsrc.GetResource(BLOCK_OS_TYPE); - - if(blkxRez == null) - throw new - ImageNotSupportedException("Image resource fork doesn't contain UDIF block chunks. Please fill an issue and send it to us."); - - if(blkxRez.GetIds().Length == 0) - throw new - ImageNotSupportedException("Image resource fork doesn't contain UDIF block chunks. Please fill an issue and send it to us."); - - blkxList.AddRange(blkxRez.GetIds().Select(blkxId => blkxRez.GetResource(blkxId))); - - Resource versRez = rsrc.GetResource(0x76657273); - - if(versRez != null) vers = versRez.GetResource(versRez.GetIds()[0]); - } - else if(footer.plistLen != 0) - { - DicConsole.DebugWriteLine("UDIF plugin", "Reading property list."); - byte[] plistB = new byte[footer.plistLen]; - stream.Seek((long)footer.plistOff, SeekOrigin.Begin); - stream.Read(plistB, 0, plistB.Length); - - DicConsole.DebugWriteLine("UDIF plugin", "Parsing property list."); - NSDictionary plist = (NSDictionary)XmlPropertyListParser.Parse(plistB); - if(plist == null) throw new Exception("Could not parse property list."); - - if(!plist.TryGetValue(RESOURCE_FORK_KEY, out NSObject rsrcObj)) - throw new Exception("Could not retrieve resource fork."); - - NSDictionary rsrc = (NSDictionary)rsrcObj; - - if(!rsrc.TryGetValue(BLOCK_KEY, out NSObject blkxObj)) - throw new Exception("Could not retrieve block chunks array."); - - NSObject[] blkx = ((NSArray)blkxObj).GetArray(); - - foreach(NSDictionary part in blkx.Cast<NSDictionary>()) - { - if(!part.TryGetValue("Name", out _)) throw new Exception("Could not retrieve Name"); - - if(!part.TryGetValue("Data", out NSObject dataObj)) throw new Exception("Could not retrieve Data"); - - blkxList.Add(((NSData)dataObj).Bytes); - } - - if(rsrc.TryGetValue("vers", out NSObject versObj)) - { - NSObject[] versArray = ((NSArray)versObj).GetArray(); - if(versArray.Length >= 1) vers = ((NSData)versArray[0]).Bytes; - } - } - else - { - // Obsolete read-only UDIF only prepended the header and then put the image without any kind of block references. - // So let's falsify a block chunk - BlockChunk bChnk = new BlockChunk - { - length = footer.dataForkLen, - offset = footer.dataForkOff, - sector = 0, - sectors = footer.sectorCount, - type = CHUNK_TYPE_COPY - }; - imageInfo.Sectors = footer.sectorCount; - chunks.Add(bChnk.sector, bChnk); - buffersize = 2048 * SECTOR_SIZE; - fakeBlockChunks = true; - } - - if(vers != null) - { - Version version = new Version(vers); - - string release = null; - string dev = null; - string pre = null; - - string major = $"{version.MajorVersion}"; - string minor = $".{version.MinorVersion / 10}"; - if(version.MinorVersion % 10 > 0) release = $".{version.MinorVersion % 10}"; - switch(version.DevStage) - { - case Version.DevelopmentStage.Alpha: - dev = "a"; - break; - case Version.DevelopmentStage.Beta: - dev = "b"; - break; - case Version.DevelopmentStage.PreAlpha: - dev = "d"; - break; - } - - if(dev == null && version.PreReleaseVersion > 0) dev = "f"; - - if(dev != null) pre = $"{version.PreReleaseVersion}"; - - imageInfo.ApplicationVersion = $"{major}{minor}{release}{dev}{pre}"; - imageInfo.Application = version.VersionString; - imageInfo.Comments = version.VersionMessage; - - if(version.MajorVersion == 3) imageInfo.Application = "ShrinkWrap™"; - else if(version.MajorVersion == 6) imageInfo.Application = "DiskCopy"; - } - else imageInfo.Application = "DiskCopy"; - - DicConsole.DebugWriteLine("UDIF plugin", "Image application = {0} version {1}", imageInfo.Application, - imageInfo.ApplicationVersion); - - imageInfo.Sectors = 0; - if(!fakeBlockChunks) - { - if(blkxList.Count == 0) - throw new - ImageNotSupportedException("Could not retrieve block chunks. Please fill an issue and send it to us."); - - buffersize = 0; - - foreach(byte[] blkxBytes in blkxList) - { - BlockHeader bHdr = new BlockHeader(); - byte[] bHdrB = new byte[Marshal.SizeOf(bHdr)]; - Array.Copy(blkxBytes, 0, bHdrB, 0, Marshal.SizeOf(bHdr)); - bHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<BlockHeader>(bHdrB); - - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.signature = 0x{0:X8}", bHdr.signature); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.version = {0}", bHdr.version); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.sectorStart = {0}", bHdr.sectorStart); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.sectorCount = {0}", bHdr.sectorCount); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.dataOffset = {0}", bHdr.dataOffset); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.buffers = {0}", bHdr.buffers); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.descriptor = 0x{0:X8}", bHdr.descriptor); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved1 = {0}", bHdr.reserved1); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved2 = {0}", bHdr.reserved2); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved3 = {0}", bHdr.reserved3); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved4 = {0}", bHdr.reserved4); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved5 = {0}", bHdr.reserved5); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved6 = {0}", bHdr.reserved6); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.checksumType = {0}", bHdr.checksumType); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.checksumLen = {0}", bHdr.checksumLen); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.checksum = 0x{0:X8}", bHdr.checksum); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunks = {0}", bHdr.chunks); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reservedChk is empty? = {0}", - ArrayHelpers.ArrayIsNullOrEmpty(bHdr.reservedChk)); - - if(bHdr.buffers > buffersize) buffersize = bHdr.buffers * SECTOR_SIZE; - - for(int i = 0; i < bHdr.chunks; i++) - { - BlockChunk bChnk = new BlockChunk(); - byte[] bChnkB = new byte[Marshal.SizeOf(bChnk)]; - Array.Copy(blkxBytes, Marshal.SizeOf(bHdr) + Marshal.SizeOf(bChnk) * i, bChnkB, 0, - Marshal.SizeOf(bChnk)); - bChnk = BigEndianMarshal.ByteArrayToStructureBigEndian<BlockChunk>(bChnkB); - - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].type = 0x{1:X8}", i, bChnk.type); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].comment = {1}", i, bChnk.comment); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].sector = {1}", i, bChnk.sector); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].sectors = {1}", i, bChnk.sectors); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].offset = {1}", i, bChnk.offset); - DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].length = {1}", i, bChnk.length); - - if(bChnk.type == CHUNK_TYPE_END) break; - - imageInfo.Sectors += bChnk.sectors; - - // Chunk offset is relative - bChnk.sector += bHdr.sectorStart; - bChnk.offset += bHdr.dataOffset; - - switch(bChnk.type) - { - // TODO: Handle comments - case CHUNK_TYPE_COMMNT: continue; - // TODO: Handle compressed chunks - case CHUNK_TYPE_KENCODE: - throw new - ImageNotSupportedException("Chunks compressed with KenCode are not yet supported."); - case CHUNK_TYPE_LZH: - throw new - ImageNotSupportedException("Chunks compressed with LZH are not yet supported."); - case CHUNK_TYPE_LZFSE: - throw new - ImageNotSupportedException("Chunks compressed with lzfse are not yet supported."); - } - - if(bChnk.type > CHUNK_TYPE_NOCOPY && bChnk.type < CHUNK_TYPE_COMMNT || - bChnk.type > CHUNK_TYPE_LZFSE && bChnk.type < CHUNK_TYPE_END) - throw new ImageNotSupportedException($"Unsupported chunk type 0x{bChnk.type:X8} found"); - - if(bChnk.sectors > 0) chunks.Add(bChnk.sector, bChnk); - } - } - } - - sectorCache = new Dictionary<ulong, byte[]>(); - chunkCache = new Dictionary<ulong, byte[]>(); - currentChunkCacheSize = 0; - imageStream = stream; - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.SectorSize = SECTOR_SIZE; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.ImageSize = imageInfo.Sectors * SECTOR_SIZE; - imageInfo.Version = $"{footer.version}"; - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - - BlockChunk readChunk = new BlockChunk(); - bool chunkFound = false; - ulong chunkStartSector = 0; - - foreach(KeyValuePair<ulong, BlockChunk> kvp in chunks.Where(kvp => sectorAddress >= kvp.Key)) - { - readChunk = kvp.Value; - chunkFound = true; - chunkStartSector = kvp.Key; - } - - long relOff = ((long)sectorAddress - (long)chunkStartSector) * SECTOR_SIZE; - - if(relOff < 0) - throw new ArgumentOutOfRangeException(nameof(relOff), - $"Got a negative offset for sector {sectorAddress}. This should not happen."); - - if(!chunkFound) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if((readChunk.type & CHUNK_TYPE_COMPRESSED_MASK) == CHUNK_TYPE_COMPRESSED_MASK) - { - if(!chunkCache.TryGetValue(chunkStartSector, out byte[] buffer)) - { - byte[] cmpBuffer = new byte[readChunk.length]; - imageStream.Seek((long)readChunk.offset, SeekOrigin.Begin); - imageStream.Read(cmpBuffer, 0, cmpBuffer.Length); - MemoryStream cmpMs = new MemoryStream(cmpBuffer); - Stream decStream = null; - - switch(readChunk.type) - { - case CHUNK_TYPE_ADC: - decStream = new ADCStream(cmpMs); - break; - case CHUNK_TYPE_ZLIB: - decStream = new ZlibStream(cmpMs, CompressionMode.Decompress); - break; - case CHUNK_TYPE_BZIP: - decStream = new BZip2Stream(cmpMs, SharpCompress.Compressors.CompressionMode.Decompress); - break; - case CHUNK_TYPE_RLE: break; - default: - throw new ImageNotSupportedException($"Unsupported chunk type 0x{readChunk.type:X8} found"); - } - - #if DEBUG - try - { - #endif - byte[] tmpBuffer; - int realSize; - switch(readChunk.type) - { - case CHUNK_TYPE_ADC: - case CHUNK_TYPE_ZLIB: - case CHUNK_TYPE_BZIP: - tmpBuffer = new byte[buffersize]; - realSize = decStream.Read(tmpBuffer, 0, (int)buffersize); - buffer = new byte[realSize]; - Array.Copy(tmpBuffer, 0, buffer, 0, realSize); - - if(currentChunkCacheSize + realSize > MAX_CACHE_SIZE) - { - chunkCache.Clear(); - currentChunkCacheSize = 0; - } - - chunkCache.Add(chunkStartSector, buffer); - currentChunkCacheSize += (uint)realSize; - break; - case CHUNK_TYPE_RLE: - tmpBuffer = new byte[buffersize]; - realSize = 0; - AppleRle rle = new AppleRle(cmpMs); - for(int i = 0; i < buffersize; i++) - { - int b = rle.ProduceByte(); - if(b == -1) break; - - tmpBuffer[i] = (byte)b; - realSize++; - } - - buffer = new byte[realSize]; - Array.Copy(tmpBuffer, 0, buffer, 0, realSize); - break; - } - #if DEBUG - } - catch(ZlibException) - { - DicConsole.WriteLine("zlib exception on chunk starting at sector {0}", readChunk.sector); - throw; - } - #endif - } - - sector = new byte[SECTOR_SIZE]; - Array.Copy(buffer, relOff, sector, 0, SECTOR_SIZE); - - if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - - return sector; - } - - switch(readChunk.type) - { - case CHUNK_TYPE_NOCOPY: - case CHUNK_TYPE_ZERO: - sector = new byte[SECTOR_SIZE]; - - if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - return sector; - case CHUNK_TYPE_COPY: - imageStream.Seek((long)readChunk.offset + relOff, SeekOrigin.Begin); - sector = new byte[SECTOR_SIZE]; - imageStream.Read(sector, 0, sector.Length); - - if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - return sector; - } - - throw new ImageNotSupportedException($"Unsupported chunk type 0x{readChunk.type:X8} found"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - MemoryStream ms = new MemoryStream(); - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".dmg"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - chunks = new Dictionary<ulong, BlockChunk>(); - currentChunk = new BlockChunk(); - currentSector = 0; - dataForkChecksum = new Crc32Context(); - masterChecksum = new Crc32Context(); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - if(sectorAddress < currentSector) - { - ErrorMessage = "Tried to rewind, this format rewinded on writing"; - return false; - } - - masterChecksum.Update(data); - - bool isEmpty = ArrayHelpers.ArrayIsNullOrEmpty(data); - - switch(currentChunk.type) - { - case CHUNK_TYPE_ZERO: - currentChunk.type = isEmpty ? CHUNK_TYPE_NOCOPY : CHUNK_TYPE_COPY; - break; - case CHUNK_TYPE_NOCOPY when !isEmpty: - case CHUNK_TYPE_COPY when isEmpty: - chunks.Add(currentChunk.sector, currentChunk); - currentChunk = new BlockChunk - { - type = isEmpty ? CHUNK_TYPE_NOCOPY : CHUNK_TYPE_COPY, - sector = currentSector, - offset = (ulong)(isEmpty ? 0 : writingStream.Position) - }; - break; - } - - currentChunk.sectors++; - currentChunk.length += (ulong)(isEmpty ? 0 : 512); - currentSector++; - - if(!isEmpty) - { - dataForkChecksum.Update(data); - writingStream.Write(data, 0, data.Length); - } - - ErrorMessage = ""; - return true; - } - - // TODO: This can be optimized - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) - { - if(currentChunk.type == CHUNK_TYPE_COPY) - { - chunks.Add(currentChunk.sector, currentChunk); - currentChunk = new BlockChunk {type = CHUNK_TYPE_NOCOPY, sector = currentSector}; - } - - currentChunk.sectors += (ulong)(data.Length / imageInfo.SectorSize); - currentSector += (ulong)(data.Length / imageInfo.SectorSize); - masterChecksum.Update(data); - - ErrorMessage = ""; - return true; - } - - for(uint i = 0; i < length; i++) - { - byte[] tmp = new byte[imageInfo.SectorSize]; - Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); - if(!WriteSector(tmp, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(currentChunk.type != CHUNK_TYPE_NOCOPY) currentChunk.length = currentChunk.sectors * 512; - chunks.Add(currentChunk.sector, currentChunk); - - chunks.Add(imageInfo.Sectors, new BlockChunk {type = CHUNK_TYPE_END, sector = imageInfo.Sectors}); - - BlockHeader bHdr = new BlockHeader - { - signature = CHUNK_SIGNATURE, - version = 1, - sectorCount = imageInfo.Sectors, - checksumType = UDIF_CHECKSUM_TYPE_CRC32, - checksumLen = 32, - checksum = BitConverter.ToUInt32(dataForkChecksum.Final().Reverse().ToArray(), 0), - chunks = (uint)chunks.Count - }; - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - MemoryStream chunkMs = new MemoryStream(); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.signature), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.version), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.sectorStart), 0, 8); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.sectorCount), 0, 8); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.dataOffset), 0, 8); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.buffers), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.descriptor), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved1), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved2), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved3), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved4), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved5), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved6), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.checksumType), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.checksumLen), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.checksum), 0, 4); - chunkMs.Write(new byte[124], 0, 124); - chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.chunks), 0, 4); - - foreach(BlockChunk chunk in chunks.Values) - { - chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.type), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.comment), 0, 4); - chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.sector), 0, 8); - chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.sectors), 0, 8); - chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.offset), 0, 8); - chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.length), 0, 8); - } - - byte[] plist = Encoding.UTF8.GetBytes(new NSDictionary - { - { - "resource-fork", - new NSDictionary - { - { - "blkx", - new NSArray - { - new NSDictionary - { - {"Attributes", "0x0050"}, - {"CFName", "whole disk (DiscImageChef : 0)"}, - {"Data", chunkMs.ToArray()}, - {"ID", "0"}, - {"Name", "whole disk (DiscImageChef : 0)"} - } - } - } - } - } - }.ToXmlPropertyList()); - - footer = new UdifFooter - { - signature = UDIF_SIGNATURE, - version = 4, - headerSize = 512, - flags = 1, - dataForkLen = (ulong)writingStream.Length, - segmentNumber = 1, - segmentCount = 1, - segmentId = Guid.NewGuid(), - dataForkChkType = UDIF_CHECKSUM_TYPE_CRC32, - dataForkChkLen = 32, - dataForkChk = BitConverter.ToUInt32(dataForkChecksum.Final().Reverse().ToArray(), 0), - plistOff = (ulong)writingStream.Length, - plistLen = (ulong)plist.Length, - // TODO: Find how is this calculated - /*masterChkType = 2, - masterChkLen = 32, - masterChk = BitConverter.ToUInt32(masterChecksum.Final().Reverse().ToArray(), 0),*/ - imageVariant = 2, - sectorCount = imageInfo.Sectors - }; - - writingStream.Seek(0, SeekOrigin.End); - writingStream.Write(plist, 0, plist.Length); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.signature), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.version), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.headerSize), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.flags), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.runningDataForkOff), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkOff), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkLen), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.rsrcForkOff), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.rsrcForkLen), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.segmentNumber), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.segmentCount), 0, 4); - writingStream.Write(footer.segmentId.ToByteArray(), 0, 16); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkChkType), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkChkLen), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkChk), 0, 4); - writingStream.Write(new byte[124], 0, 124); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.plistOff), 0, 8); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.plistLen), 0, 8); - writingStream.Write(new byte[120], 0, 120); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.masterChkType), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.masterChkLen), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.masterChk), 0, 4); - writingStream.Write(new byte[124], 0, 124); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.imageVariant), 0, 4); - writingStream.Write(BigEndianBitConverter.GetBytes(footer.sectorCount), 0, 8); - writingStream.Write(new byte[12], 0, 12); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - // TODO: Comments - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct UdifFooter - { - public uint signature; - public uint version; - public uint headerSize; - public uint flags; - public ulong runningDataForkOff; - public ulong dataForkOff; - public ulong dataForkLen; - public ulong rsrcForkOff; - public ulong rsrcForkLen; - public uint segmentNumber; - public uint segmentCount; - public Guid segmentId; - public uint dataForkChkType; - public uint dataForkChkLen; - public uint dataForkChk; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 124)] - public byte[] reserved1; - public ulong plistOff; - public ulong plistLen; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 120)] - public byte[] reserved2; - public uint masterChkType; - public uint masterChkLen; - public uint masterChk; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 124)] - public byte[] reserved3; - public uint imageVariant; - public ulong sectorCount; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] - public byte[] reserved4; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct BlockHeader - { - public uint signature; - public uint version; - public ulong sectorStart; - public ulong sectorCount; - public ulong dataOffset; - public uint buffers; - public uint descriptor; - public uint reserved1; - public uint reserved2; - public uint reserved3; - public uint reserved4; - public uint reserved5; - public uint reserved6; - public uint checksumType; - public uint checksumLen; - public uint checksum; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 124)] - public byte[] reservedChk; - public uint chunks; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct BlockChunk - { - public uint type; - public uint comment; - public ulong sector; - public ulong sectors; - public ulong offset; - public ulong length; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/Constants.cs b/DiscImageChef.DiscImages/UDIF/Constants.cs new file mode 100644 index 000000000..922454d38 --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/Constants.cs @@ -0,0 +1,74 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Udif + { + const uint UDIF_SIGNATURE = 0x6B6F6C79; + const uint CHUNK_SIGNATURE = 0x6D697368; + + // All chunk types with this mask are compressed + const uint CHUNK_TYPE_COMPRESSED_MASK = 0x80000000; + + const uint CHUNK_TYPE_ZERO = 0x00000000; + const uint CHUNK_TYPE_COPY = 0x00000001; + const uint CHUNK_TYPE_NOCOPY = 0x00000002; + const uint CHUNK_TYPE_KENCODE = 0x80000001; + const uint CHUNK_TYPE_RLE = 0x80000002; + const uint CHUNK_TYPE_LZH = 0x80000003; + const uint CHUNK_TYPE_ADC = 0x80000004; + const uint CHUNK_TYPE_ZLIB = 0x80000005; + const uint CHUNK_TYPE_BZIP = 0x80000006; + const uint CHUNK_TYPE_LZFSE = 0x80000007; + const uint CHUNK_TYPE_COMMNT = 0x7FFFFFFE; + const uint CHUNK_TYPE_END = 0xFFFFFFFF; + + const uint UDIF_CHECKSUM_TYPE_NONE = 0; + const uint UDIF_CHECKSUM_TYPE_CRC28 = 1; + const uint UDIF_CHECKSUM_TYPE_CRC32 = 2; + const uint UDIF_CHECKSUM_TYPE_DC42 = 3; + const uint UDIF_CHECKSUM_TYPE_MD5 = 4; + const uint UDIF_CHECKSUM_TYPE_SHA = 5; + const uint UDIF_CHECKSUM_TYPE_SHA1 = 6; + const uint UDIF_CHECKSUM_TYPE_SHA256 = 7; + const uint UDIF_CHECKSUM_TYPE_SHA384 = 8; + const uint UDIF_CHECKSUM_TYPE_SHA512 = 9; + + const string RESOURCE_FORK_KEY = "resource-fork"; + const string BLOCK_KEY = "blkx"; + const uint BLOCK_OS_TYPE = 0x626C6B78; + const uint MAX_CACHE_SIZE = 16777216; + const uint SECTOR_SIZE = 512; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/Identify.cs b/DiscImageChef.DiscImages/UDIF/Identify.cs new file mode 100644 index 000000000..d8d7903d7 --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/Identify.cs @@ -0,0 +1,65 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Udif + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if(stream.Length < 512) return false; + + stream.Seek(-Marshal.SizeOf(footer), SeekOrigin.End); + byte[] footerB = new byte[Marshal.SizeOf(footer)]; + + stream.Read(footerB, 0, Marshal.SizeOf(footer)); + footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(footerB); + + if(footer.signature == UDIF_SIGNATURE) return true; + + // Old UDIF as created by DiskCopy 6.5 using "OBSOLETE" format. (DiskCopy 5 rumored format?) + stream.Seek(0, SeekOrigin.Begin); + byte[] headerB = new byte[Marshal.SizeOf(footer)]; + + stream.Read(headerB, 0, Marshal.SizeOf(footer)); + footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(headerB); + + return footer.signature == UDIF_SIGNATURE; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/Properties.cs b/DiscImageChef.DiscImages/UDIF/Properties.cs new file mode 100644 index 000000000..153923281 --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/Properties.cs @@ -0,0 +1,78 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Udif + { + public ImageInfo Info => imageInfo; + + public string Name => "Apple Universal Disk Image Format"; + public Guid Id => new Guid("5BEB9002-CF3D-429C-8E06-9A96F49203FF"); + + public string Format => "Apple Universal Disk Image Format"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".dmg"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/Read.cs b/DiscImageChef.DiscImages/UDIF/Read.cs new file mode 100644 index 000000000..5c6fcb3bb --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/Read.cs @@ -0,0 +1,510 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Claunia.PropertyList; +using Claunia.RsrcFork; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Compression; +using DiscImageChef.Console; +using Ionic.Zlib; +using SharpCompress.Compressors.ADC; +using SharpCompress.Compressors.BZip2; +using Version = Resources.Version; + +namespace DiscImageChef.DiscImages +{ + public partial class Udif + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + if(stream.Length < 512) return false; + + stream.Seek(-Marshal.SizeOf(footer), SeekOrigin.End); + byte[] footerB = new byte[Marshal.SizeOf(footer)]; + + stream.Read(footerB, 0, Marshal.SizeOf(footer)); + footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(footerB); + + if(footer.signature != UDIF_SIGNATURE) + { + stream.Seek(0, SeekOrigin.Begin); + footerB = new byte[Marshal.SizeOf(footer)]; + + stream.Read(footerB, 0, Marshal.SizeOf(footer)); + footer = BigEndianMarshal.ByteArrayToStructureBigEndian<UdifFooter>(footerB); + + if(footer.signature != UDIF_SIGNATURE) throw new Exception("Unable to find UDIF signature."); + + DicConsole.VerboseWriteLine("Found obsolete UDIF format."); + } + + DicConsole.DebugWriteLine("UDIF plugin", "footer.signature = 0x{0:X8}", footer.signature); + DicConsole.DebugWriteLine("UDIF plugin", "footer.version = {0}", footer.version); + DicConsole.DebugWriteLine("UDIF plugin", "footer.headerSize = {0}", footer.headerSize); + DicConsole.DebugWriteLine("UDIF plugin", "footer.flags = {0}", footer.flags); + DicConsole.DebugWriteLine("UDIF plugin", "footer.runningDataForkOff = {0}", footer.runningDataForkOff); + DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkOff = {0}", footer.dataForkOff); + DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkLen = {0}", footer.dataForkLen); + DicConsole.DebugWriteLine("UDIF plugin", "footer.rsrcForkOff = {0}", footer.rsrcForkOff); + DicConsole.DebugWriteLine("UDIF plugin", "footer.rsrcForkLen = {0}", footer.rsrcForkLen); + DicConsole.DebugWriteLine("UDIF plugin", "footer.segmentNumber = {0}", footer.segmentNumber); + DicConsole.DebugWriteLine("UDIF plugin", "footer.segmentCount = {0}", footer.segmentCount); + DicConsole.DebugWriteLine("UDIF plugin", "footer.segmentId = {0}", footer.segmentId); + DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkChkType = {0}", footer.dataForkChkType); + DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkLen = {0}", footer.dataForkLen); + DicConsole.DebugWriteLine("UDIF plugin", "footer.dataForkChk = 0x{0:X8}", footer.dataForkChk); + DicConsole.DebugWriteLine("UDIF plugin", "footer.plistOff = {0}", footer.plistOff); + DicConsole.DebugWriteLine("UDIF plugin", "footer.plistLen = {0}", footer.plistLen); + DicConsole.DebugWriteLine("UDIF plugin", "footer.masterChkType = {0}", footer.masterChkType); + DicConsole.DebugWriteLine("UDIF plugin", "footer.masterChkLen = {0}", footer.masterChkLen); + DicConsole.DebugWriteLine("UDIF plugin", "footer.masterChk = 0x{0:X8}", footer.masterChk); + DicConsole.DebugWriteLine("UDIF plugin", "footer.imageVariant = {0}", footer.imageVariant); + DicConsole.DebugWriteLine("UDIF plugin", "footer.sectorCount = {0}", footer.sectorCount); + DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved1 is empty? = {0}", + ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved1)); + DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved2 is empty? = {0}", + ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved2)); + DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved3 is empty? = {0}", + ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved3)); + DicConsole.DebugWriteLine("UDIF plugin", "footer.reserved4 is empty? = {0}", + ArrayHelpers.ArrayIsNullOrEmpty(footer.reserved4)); + + // Block chunks and headers + List<byte[]> blkxList = new List<byte[]>(); + chunks = new Dictionary<ulong, BlockChunk>(); + + bool fakeBlockChunks = false; + + byte[] vers = null; + + if(footer.plistLen == 0 && footer.rsrcForkLen != 0) + { + DicConsole.DebugWriteLine("UDIF plugin", "Reading resource fork."); + byte[] rsrcB = new byte[footer.rsrcForkLen]; + stream.Seek((long)footer.rsrcForkOff, SeekOrigin.Begin); + stream.Read(rsrcB, 0, rsrcB.Length); + + ResourceFork rsrc = new ResourceFork(rsrcB); + + if(!rsrc.ContainsKey(BLOCK_OS_TYPE)) + throw new + ImageNotSupportedException("Image resource fork doesn't contain UDIF block chunks. Please fill an issue and send it to us."); + + Resource blkxRez = rsrc.GetResource(BLOCK_OS_TYPE); + + if(blkxRez == null) + throw new + ImageNotSupportedException("Image resource fork doesn't contain UDIF block chunks. Please fill an issue and send it to us."); + + if(blkxRez.GetIds().Length == 0) + throw new + ImageNotSupportedException("Image resource fork doesn't contain UDIF block chunks. Please fill an issue and send it to us."); + + blkxList.AddRange(blkxRez.GetIds().Select(blkxId => blkxRez.GetResource(blkxId))); + + Resource versRez = rsrc.GetResource(0x76657273); + + if(versRez != null) vers = versRez.GetResource(versRez.GetIds()[0]); + } + else if(footer.plistLen != 0) + { + DicConsole.DebugWriteLine("UDIF plugin", "Reading property list."); + byte[] plistB = new byte[footer.plistLen]; + stream.Seek((long)footer.plistOff, SeekOrigin.Begin); + stream.Read(plistB, 0, plistB.Length); + + DicConsole.DebugWriteLine("UDIF plugin", "Parsing property list."); + NSDictionary plist = (NSDictionary)XmlPropertyListParser.Parse(plistB); + if(plist == null) throw new Exception("Could not parse property list."); + + if(!plist.TryGetValue(RESOURCE_FORK_KEY, out NSObject rsrcObj)) + throw new Exception("Could not retrieve resource fork."); + + NSDictionary rsrc = (NSDictionary)rsrcObj; + + if(!rsrc.TryGetValue(BLOCK_KEY, out NSObject blkxObj)) + throw new Exception("Could not retrieve block chunks array."); + + NSObject[] blkx = ((NSArray)blkxObj).GetArray(); + + foreach(NSDictionary part in blkx.Cast<NSDictionary>()) + { + if(!part.TryGetValue("Name", out _)) throw new Exception("Could not retrieve Name"); + + if(!part.TryGetValue("Data", out NSObject dataObj)) throw new Exception("Could not retrieve Data"); + + blkxList.Add(((NSData)dataObj).Bytes); + } + + if(rsrc.TryGetValue("vers", out NSObject versObj)) + { + NSObject[] versArray = ((NSArray)versObj).GetArray(); + if(versArray.Length >= 1) vers = ((NSData)versArray[0]).Bytes; + } + } + else + { + // Obsolete read-only UDIF only prepended the header and then put the image without any kind of block references. + // So let's falsify a block chunk + BlockChunk bChnk = new BlockChunk + { + length = footer.dataForkLen, + offset = footer.dataForkOff, + sector = 0, + sectors = footer.sectorCount, + type = CHUNK_TYPE_COPY + }; + imageInfo.Sectors = footer.sectorCount; + chunks.Add(bChnk.sector, bChnk); + buffersize = 2048 * SECTOR_SIZE; + fakeBlockChunks = true; + } + + if(vers != null) + { + Version version = new Version(vers); + + string release = null; + string dev = null; + string pre = null; + + string major = $"{version.MajorVersion}"; + string minor = $".{version.MinorVersion / 10}"; + if(version.MinorVersion % 10 > 0) release = $".{version.MinorVersion % 10}"; + switch(version.DevStage) + { + case Version.DevelopmentStage.Alpha: + dev = "a"; + break; + case Version.DevelopmentStage.Beta: + dev = "b"; + break; + case Version.DevelopmentStage.PreAlpha: + dev = "d"; + break; + } + + if(dev == null && version.PreReleaseVersion > 0) dev = "f"; + + if(dev != null) pre = $"{version.PreReleaseVersion}"; + + imageInfo.ApplicationVersion = $"{major}{minor}{release}{dev}{pre}"; + imageInfo.Application = version.VersionString; + imageInfo.Comments = version.VersionMessage; + + if(version.MajorVersion == 3) imageInfo.Application = "ShrinkWrap™"; + else if(version.MajorVersion == 6) imageInfo.Application = "DiskCopy"; + } + else imageInfo.Application = "DiskCopy"; + + DicConsole.DebugWriteLine("UDIF plugin", "Image application = {0} version {1}", imageInfo.Application, + imageInfo.ApplicationVersion); + + imageInfo.Sectors = 0; + if(!fakeBlockChunks) + { + if(blkxList.Count == 0) + throw new + ImageNotSupportedException("Could not retrieve block chunks. Please fill an issue and send it to us."); + + buffersize = 0; + + foreach(byte[] blkxBytes in blkxList) + { + BlockHeader bHdr = new BlockHeader(); + byte[] bHdrB = new byte[Marshal.SizeOf(bHdr)]; + Array.Copy(blkxBytes, 0, bHdrB, 0, Marshal.SizeOf(bHdr)); + bHdr = BigEndianMarshal.ByteArrayToStructureBigEndian<BlockHeader>(bHdrB); + + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.signature = 0x{0:X8}", bHdr.signature); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.version = {0}", bHdr.version); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.sectorStart = {0}", bHdr.sectorStart); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.sectorCount = {0}", bHdr.sectorCount); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.dataOffset = {0}", bHdr.dataOffset); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.buffers = {0}", bHdr.buffers); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.descriptor = 0x{0:X8}", bHdr.descriptor); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved1 = {0}", bHdr.reserved1); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved2 = {0}", bHdr.reserved2); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved3 = {0}", bHdr.reserved3); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved4 = {0}", bHdr.reserved4); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved5 = {0}", bHdr.reserved5); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reserved6 = {0}", bHdr.reserved6); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.checksumType = {0}", bHdr.checksumType); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.checksumLen = {0}", bHdr.checksumLen); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.checksum = 0x{0:X8}", bHdr.checksum); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunks = {0}", bHdr.chunks); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.reservedChk is empty? = {0}", + ArrayHelpers.ArrayIsNullOrEmpty(bHdr.reservedChk)); + + if(bHdr.buffers > buffersize) buffersize = bHdr.buffers * SECTOR_SIZE; + + for(int i = 0; i < bHdr.chunks; i++) + { + BlockChunk bChnk = new BlockChunk(); + byte[] bChnkB = new byte[Marshal.SizeOf(bChnk)]; + Array.Copy(blkxBytes, Marshal.SizeOf(bHdr) + Marshal.SizeOf(bChnk) * i, bChnkB, 0, + Marshal.SizeOf(bChnk)); + bChnk = BigEndianMarshal.ByteArrayToStructureBigEndian<BlockChunk>(bChnkB); + + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].type = 0x{1:X8}", i, bChnk.type); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].comment = {1}", i, bChnk.comment); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].sector = {1}", i, bChnk.sector); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].sectors = {1}", i, bChnk.sectors); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].offset = {1}", i, bChnk.offset); + DicConsole.DebugWriteLine("UDIF plugin", "bHdr.chunk[{0}].length = {1}", i, bChnk.length); + + if(bChnk.type == CHUNK_TYPE_END) break; + + imageInfo.Sectors += bChnk.sectors; + + // Chunk offset is relative + bChnk.sector += bHdr.sectorStart; + bChnk.offset += bHdr.dataOffset; + + switch(bChnk.type) + { + // TODO: Handle comments + case CHUNK_TYPE_COMMNT: continue; + // TODO: Handle compressed chunks + case CHUNK_TYPE_KENCODE: + throw new + ImageNotSupportedException("Chunks compressed with KenCode are not yet supported."); + case CHUNK_TYPE_LZH: + throw new + ImageNotSupportedException("Chunks compressed with LZH are not yet supported."); + case CHUNK_TYPE_LZFSE: + throw new + ImageNotSupportedException("Chunks compressed with lzfse are not yet supported."); + } + + if(bChnk.type > CHUNK_TYPE_NOCOPY && bChnk.type < CHUNK_TYPE_COMMNT || + bChnk.type > CHUNK_TYPE_LZFSE && bChnk.type < CHUNK_TYPE_END) + throw new ImageNotSupportedException($"Unsupported chunk type 0x{bChnk.type:X8} found"); + + if(bChnk.sectors > 0) chunks.Add(bChnk.sector, bChnk); + } + } + } + + sectorCache = new Dictionary<ulong, byte[]>(); + chunkCache = new Dictionary<ulong, byte[]>(); + currentChunkCacheSize = 0; + imageStream = stream; + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.SectorSize = SECTOR_SIZE; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.ImageSize = imageInfo.Sectors * SECTOR_SIZE; + imageInfo.Version = $"{footer.version}"; + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; + + BlockChunk readChunk = new BlockChunk(); + bool chunkFound = false; + ulong chunkStartSector = 0; + + foreach(KeyValuePair<ulong, BlockChunk> kvp in chunks.Where(kvp => sectorAddress >= kvp.Key)) + { + readChunk = kvp.Value; + chunkFound = true; + chunkStartSector = kvp.Key; + } + + long relOff = ((long)sectorAddress - (long)chunkStartSector) * SECTOR_SIZE; + + if(relOff < 0) + throw new ArgumentOutOfRangeException(nameof(relOff), + $"Got a negative offset for sector {sectorAddress}. This should not happen."); + + if(!chunkFound) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if((readChunk.type & CHUNK_TYPE_COMPRESSED_MASK) == CHUNK_TYPE_COMPRESSED_MASK) + { + if(!chunkCache.TryGetValue(chunkStartSector, out byte[] buffer)) + { + byte[] cmpBuffer = new byte[readChunk.length]; + imageStream.Seek((long)readChunk.offset, SeekOrigin.Begin); + imageStream.Read(cmpBuffer, 0, cmpBuffer.Length); + MemoryStream cmpMs = new MemoryStream(cmpBuffer); + Stream decStream = null; + + switch(readChunk.type) + { + case CHUNK_TYPE_ADC: + decStream = new ADCStream(cmpMs); + break; + case CHUNK_TYPE_ZLIB: + decStream = new ZlibStream(cmpMs, CompressionMode.Decompress); + break; + case CHUNK_TYPE_BZIP: + decStream = new BZip2Stream(cmpMs, SharpCompress.Compressors.CompressionMode.Decompress); + break; + case CHUNK_TYPE_RLE: break; + default: + throw new ImageNotSupportedException($"Unsupported chunk type 0x{readChunk.type:X8} found"); + } + + #if DEBUG + try + { + #endif + byte[] tmpBuffer; + int realSize; + switch(readChunk.type) + { + case CHUNK_TYPE_ADC: + case CHUNK_TYPE_ZLIB: + case CHUNK_TYPE_BZIP: + tmpBuffer = new byte[buffersize]; + realSize = decStream.Read(tmpBuffer, 0, (int)buffersize); + buffer = new byte[realSize]; + Array.Copy(tmpBuffer, 0, buffer, 0, realSize); + + if(currentChunkCacheSize + realSize > MAX_CACHE_SIZE) + { + chunkCache.Clear(); + currentChunkCacheSize = 0; + } + + chunkCache.Add(chunkStartSector, buffer); + currentChunkCacheSize += (uint)realSize; + break; + case CHUNK_TYPE_RLE: + tmpBuffer = new byte[buffersize]; + realSize = 0; + AppleRle rle = new AppleRle(cmpMs); + for(int i = 0; i < buffersize; i++) + { + int b = rle.ProduceByte(); + if(b == -1) break; + + tmpBuffer[i] = (byte)b; + realSize++; + } + + buffer = new byte[realSize]; + Array.Copy(tmpBuffer, 0, buffer, 0, realSize); + break; + } + #if DEBUG + } + catch(ZlibException) + { + DicConsole.WriteLine("zlib exception on chunk starting at sector {0}", readChunk.sector); + throw; + } + #endif + } + + sector = new byte[SECTOR_SIZE]; + Array.Copy(buffer, relOff, sector, 0, SECTOR_SIZE); + + if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + switch(readChunk.type) + { + case CHUNK_TYPE_NOCOPY: + case CHUNK_TYPE_ZERO: + sector = new byte[SECTOR_SIZE]; + + if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + return sector; + case CHUNK_TYPE_COPY: + imageStream.Seek((long)readChunk.offset + relOff, SeekOrigin.Begin); + sector = new byte[SECTOR_SIZE]; + imageStream.Read(sector, 0, sector.Length); + + if(sectorCache.Count >= MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + return sector; + } + + throw new ImageNotSupportedException($"Unsupported chunk type 0x{readChunk.type:X8} found"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/Structs.cs b/DiscImageChef.DiscImages/UDIF/Structs.cs new file mode 100644 index 000000000..326b428d0 --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/Structs.cs @@ -0,0 +1,110 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Udif + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct UdifFooter + { + public uint signature; + public uint version; + public uint headerSize; + public uint flags; + public ulong runningDataForkOff; + public ulong dataForkOff; + public ulong dataForkLen; + public ulong rsrcForkOff; + public ulong rsrcForkLen; + public uint segmentNumber; + public uint segmentCount; + public Guid segmentId; + public uint dataForkChkType; + public uint dataForkChkLen; + public uint dataForkChk; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 124)] + public byte[] reserved1; + public ulong plistOff; + public ulong plistLen; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 120)] + public byte[] reserved2; + public uint masterChkType; + public uint masterChkLen; + public uint masterChk; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 124)] + public byte[] reserved3; + public uint imageVariant; + public ulong sectorCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public byte[] reserved4; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct BlockHeader + { + public uint signature; + public uint version; + public ulong sectorStart; + public ulong sectorCount; + public ulong dataOffset; + public uint buffers; + public uint descriptor; + public uint reserved1; + public uint reserved2; + public uint reserved3; + public uint reserved4; + public uint reserved5; + public uint reserved6; + public uint checksumType; + public uint checksumLen; + public uint checksum; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 124)] + public byte[] reservedChk; + public uint chunks; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct BlockChunk + { + public uint type; + public uint comment; + public ulong sector; + public ulong sectors; + public ulong offset; + public ulong length; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/UDIF.cs b/DiscImageChef.DiscImages/UDIF/UDIF.cs new file mode 100644 index 000000000..9cbd674ac --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/UDIF.cs @@ -0,0 +1,87 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : UDIF.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +#pragma warning disable 612 + +namespace DiscImageChef.DiscImages +{ + public partial class Udif : IWritableImage + { + uint buffersize; + Dictionary<ulong, byte[]> chunkCache; + Dictionary<ulong, BlockChunk> chunks; + BlockChunk currentChunk; + uint currentChunkCacheSize; + ulong currentSector; + Crc32Context dataForkChecksum; + UdifFooter footer; + ImageInfo imageInfo; + Stream imageStream; + Crc32Context masterChecksum; + Dictionary<ulong, byte[]> sectorCache; + FileStream writingStream; + + public Udif() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/Unsupported.cs b/DiscImageChef.DiscImages/UDIF/Unsupported.cs new file mode 100644 index 000000000..45daf54de --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Udif + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UDIF/Write.cs b/DiscImageChef.DiscImages/UDIF/Write.cs new file mode 100644 index 000000000..bcfb9b03a --- /dev/null +++ b/DiscImageChef.DiscImages/UDIF/Write.cs @@ -0,0 +1,393 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Apple Universal Disk Image Format. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Claunia.PropertyList; +using DiscImageChef.Checksums; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Udif + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + chunks = new Dictionary<ulong, BlockChunk>(); + currentChunk = new BlockChunk(); + currentSector = 0; + dataForkChecksum = new Crc32Context(); + masterChecksum = new Crc32Context(); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + if(sectorAddress < currentSector) + { + ErrorMessage = "Tried to rewind, this format rewinded on writing"; + return false; + } + + masterChecksum.Update(data); + + bool isEmpty = ArrayHelpers.ArrayIsNullOrEmpty(data); + + switch(currentChunk.type) + { + case CHUNK_TYPE_ZERO: + currentChunk.type = isEmpty ? CHUNK_TYPE_NOCOPY : CHUNK_TYPE_COPY; + break; + case CHUNK_TYPE_NOCOPY when !isEmpty: + case CHUNK_TYPE_COPY when isEmpty: + chunks.Add(currentChunk.sector, currentChunk); + currentChunk = new BlockChunk + { + type = isEmpty ? CHUNK_TYPE_NOCOPY : CHUNK_TYPE_COPY, + sector = currentSector, + offset = (ulong)(isEmpty ? 0 : writingStream.Position) + }; + break; + } + + currentChunk.sectors++; + currentChunk.length += (ulong)(isEmpty ? 0 : 512); + currentSector++; + + if(!isEmpty) + { + dataForkChecksum.Update(data); + writingStream.Write(data, 0, data.Length); + } + + ErrorMessage = ""; + return true; + } + + // TODO: This can be optimized + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) + { + if(currentChunk.type == CHUNK_TYPE_COPY) + { + chunks.Add(currentChunk.sector, currentChunk); + currentChunk = new BlockChunk {type = CHUNK_TYPE_NOCOPY, sector = currentSector}; + } + + currentChunk.sectors += (ulong)(data.Length / imageInfo.SectorSize); + currentSector += (ulong)(data.Length / imageInfo.SectorSize); + masterChecksum.Update(data); + + ErrorMessage = ""; + return true; + } + + for(uint i = 0; i < length; i++) + { + byte[] tmp = new byte[imageInfo.SectorSize]; + Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); + if(!WriteSector(tmp, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(currentChunk.type != CHUNK_TYPE_NOCOPY) currentChunk.length = currentChunk.sectors * 512; + chunks.Add(currentChunk.sector, currentChunk); + + chunks.Add(imageInfo.Sectors, new BlockChunk {type = CHUNK_TYPE_END, sector = imageInfo.Sectors}); + + BlockHeader bHdr = new BlockHeader + { + signature = CHUNK_SIGNATURE, + version = 1, + sectorCount = imageInfo.Sectors, + checksumType = UDIF_CHECKSUM_TYPE_CRC32, + checksumLen = 32, + checksum = BitConverter.ToUInt32(dataForkChecksum.Final().Reverse().ToArray(), 0), + chunks = (uint)chunks.Count + }; + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + MemoryStream chunkMs = new MemoryStream(); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.signature), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.version), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.sectorStart), 0, 8); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.sectorCount), 0, 8); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.dataOffset), 0, 8); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.buffers), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.descriptor), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved1), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved2), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved3), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved4), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved5), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.reserved6), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.checksumType), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.checksumLen), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.checksum), 0, 4); + chunkMs.Write(new byte[124], 0, 124); + chunkMs.Write(BigEndianBitConverter.GetBytes(bHdr.chunks), 0, 4); + + foreach(BlockChunk chunk in chunks.Values) + { + chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.type), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.comment), 0, 4); + chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.sector), 0, 8); + chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.sectors), 0, 8); + chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.offset), 0, 8); + chunkMs.Write(BigEndianBitConverter.GetBytes(chunk.length), 0, 8); + } + + byte[] plist = Encoding.UTF8.GetBytes(new NSDictionary + { + { + "resource-fork", + new NSDictionary + { + { + "blkx", + new NSArray + { + new NSDictionary + { + {"Attributes", "0x0050"}, + {"CFName", "whole disk (DiscImageChef : 0)"}, + {"Data", chunkMs.ToArray()}, + {"ID", "0"}, + {"Name", "whole disk (DiscImageChef : 0)"} + } + } + } + } + } + }.ToXmlPropertyList()); + + footer = new UdifFooter + { + signature = UDIF_SIGNATURE, + version = 4, + headerSize = 512, + flags = 1, + dataForkLen = (ulong)writingStream.Length, + segmentNumber = 1, + segmentCount = 1, + segmentId = Guid.NewGuid(), + dataForkChkType = UDIF_CHECKSUM_TYPE_CRC32, + dataForkChkLen = 32, + dataForkChk = BitConverter.ToUInt32(dataForkChecksum.Final().Reverse().ToArray(), 0), + plistOff = (ulong)writingStream.Length, + plistLen = (ulong)plist.Length, + // TODO: Find how is this calculated + /*masterChkType = 2, + masterChkLen = 32, + masterChk = BitConverter.ToUInt32(masterChecksum.Final().Reverse().ToArray(), 0),*/ + imageVariant = 2, + sectorCount = imageInfo.Sectors + }; + + writingStream.Seek(0, SeekOrigin.End); + writingStream.Write(plist, 0, plist.Length); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.signature), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.version), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.headerSize), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.flags), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.runningDataForkOff), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkOff), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkLen), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.rsrcForkOff), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.rsrcForkLen), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.segmentNumber), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.segmentCount), 0, 4); + writingStream.Write(footer.segmentId.ToByteArray(), 0, 16); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkChkType), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkChkLen), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.dataForkChk), 0, 4); + writingStream.Write(new byte[124], 0, 124); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.plistOff), 0, 8); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.plistLen), 0, 8); + writingStream.Write(new byte[120], 0, 120); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.masterChkType), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.masterChkLen), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.masterChk), 0, 4); + writingStream.Write(new byte[124], 0, 124); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.imageVariant), 0, 4); + writingStream.Write(BigEndianBitConverter.GetBytes(footer.sectorCount), 0, 8); + writingStream.Write(new byte[12], 0, 12); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + // TODO: Comments + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/Constants.cs b/DiscImageChef.DiscImages/UkvFdi/Constants.cs new file mode 100644 index 000000000..3d67811e8 --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/Constants.cs @@ -0,0 +1,39 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi + { + readonly byte[] signature = {0x46, 0x44, 0x49}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/Enums.cs b/DiscImageChef.DiscImages/UkvFdi/Enums.cs new file mode 100644 index 000000000..3eb81d2c5 --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/Enums.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi + { + [Flags] + enum DiskFlags : byte + { + WriteProtected = 1 + } + + [Flags] + enum SectorFlags : byte + { + CrcOk128 = 0x01, + CrcOk256 = 0x02, + CrcOk512 = 0x04, + CrcOk1024 = 0x08, + CrcOk2048 = 0x10, + CrcOk4096 = 0x20, + Deleted = 0x80 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/Helpers.cs b/DiscImageChef.DiscImages/UkvFdi/Helpers.cs new file mode 100644 index 000000000..cdcaf6ae5 --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/Helpers.cs @@ -0,0 +1,46 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi + { + (ushort cylinder, byte head, byte sector) LbaToChs(ulong lba) + { + ushort cylinder = (ushort)(lba / (imageInfo.Heads * imageInfo.SectorsPerTrack)); + byte head = (byte)(lba / imageInfo.SectorsPerTrack % imageInfo.Heads); + byte sector = (byte)(lba % imageInfo.SectorsPerTrack + 1); + + return (cylinder, head, sector); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/Identify.cs b/DiscImageChef.DiscImages/UkvFdi/Identify.cs new file mode 100644 index 000000000..fa7901e93 --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/Identify.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + FdiHeader hdr = new FdiHeader(); + + if(stream.Length < Marshal.SizeOf(hdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(hdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + hdr = (FdiHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(FdiHeader)); + handle.Free(); + + return hdr.magic.SequenceEqual(signature); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/Properties.cs b/DiscImageChef.DiscImages/UkvFdi/Properties.cs new file mode 100644 index 000000000..d151f43d6 --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/Properties.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi + { + public string Name => "Spectrum Floppy Disk Image"; + public Guid Id => new Guid("DADFC9B2-67C1-42A3-B124-825528163FC0"); + public string Format => "Spectrum floppy disk image"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public ImageInfo Info => imageInfo; + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi.cs b/DiscImageChef.DiscImages/UkvFdi/Read.cs similarity index 55% rename from DiscImageChef.DiscImages/UkvFdi.cs rename to DiscImageChef.DiscImages/UkvFdi/Read.cs index 53a84ee2d..fa5a1e2e0 100644 --- a/DiscImageChef.DiscImages/UkvFdi.cs +++ b/DiscImageChef.DiscImages/UkvFdi/Read.cs @@ -2,14 +2,14 @@ // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : UkvFdi.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Spectrum FDI disk images. +// Reads Spectrum FDI disk images. // // --[ License ] -------------------------------------------------------------- // @@ -31,89 +31,17 @@ // ****************************************************************************/ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using Schemas; namespace DiscImageChef.DiscImages { - public class UkvFdi : IMediaImage + public partial class UkvFdi { - readonly byte[] signature = {0x46, 0x44, 0x49}; - ImageInfo imageInfo; - - // Cylinder by head, sector data matrix - byte[][][][] sectorsData; - - public UkvFdi() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "Spectrum Floppy Disk Image"; - public Guid Id => new Guid("DADFC9B2-67C1-42A3-B124-825528163FC0"); - - public string Format => "Spectrum floppy disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - public ImageInfo Info => imageInfo; - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - FdiHeader hdr = new FdiHeader(); - - if(stream.Length < Marshal.SizeOf(hdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(hdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - hdr = (FdiHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(FdiHeader)); - handle.Free(); - - return hdr.magic.SequenceEqual(signature); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -281,144 +209,5 @@ namespace DiscImageChef.DiscImages return buffer.ToArray(); } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - (ushort cylinder, byte head, byte sector) LbaToChs(ulong lba) - { - ushort cylinder = (ushort)(lba / (imageInfo.Heads * imageInfo.SectorsPerTrack)); - byte head = (byte)(lba / imageInfo.SectorsPerTrack % imageInfo.Heads); - byte sector = (byte)(lba % imageInfo.SectorsPerTrack + 1); - - return (cylinder, head, sector); - } - - [Flags] - enum DiskFlags : byte - { - WriteProtected = 1 - } - - [Flags] - enum SectorFlags : byte - { - CrcOk128 = 0x01, - CrcOk256 = 0x02, - CrcOk512 = 0x04, - CrcOk1024 = 0x08, - CrcOk2048 = 0x10, - CrcOk4096 = 0x20, - Deleted = 0x80 - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct FdiHeader - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] magic; - public DiskFlags flags; - public ushort cylinders; - public ushort heads; - public ushort descOff; - public ushort dataOff; - public ushort addInfoLen; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/Structs.cs b/DiscImageChef.DiscImages/UkvFdi/Structs.cs new file mode 100644 index 000000000..94b9c9739 --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/Structs.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct FdiHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] magic; + public DiskFlags flags; + public ushort cylinders; + public ushort heads; + public ushort descOff; + public ushort dataOff; + public ushort addInfoLen; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/UkvFdi.cs b/DiscImageChef.DiscImages/UkvFdi/UkvFdi.cs new file mode 100644 index 000000000..89fe9992f --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/UkvFdi.cs @@ -0,0 +1,73 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : UkvFdi.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi : IMediaImage + { + ImageInfo imageInfo; + // Cylinder by head, sector data matrix + byte[][][][] sectorsData; + + public UkvFdi() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/UkvFdi/Unsupported.cs b/DiscImageChef.DiscImages/UkvFdi/Unsupported.cs new file mode 100644 index 000000000..39cd9e69c --- /dev/null +++ b/DiscImageChef.DiscImages/UkvFdi/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Spectrum FDI disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class UkvFdi + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI.cs b/DiscImageChef.DiscImages/VDI.cs deleted file mode 100644 index c22662219..000000000 --- a/DiscImageChef.DiscImages/VDI.cs +++ /dev/null @@ -1,726 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : VDI.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages VirtualBox disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - // TODO: Support version 0 - // TODO: Support fixed images - // TODO: Support version 1.2 geometry - public class Vdi : IWritableImage - { - const uint VDI_MAGIC = 0xBEDA107F; - const uint VDI_EMPTY = 0xFFFFFFFF; - - const string ORACLE_VDI = "<<< Oracle VM VirtualBox Disk Image >>>\n"; - const string QEMUVDI = "<<< QEMU VM Virtual Disk Image >>>\n"; - const string SUN_OLD_VDI = "<<< Sun xVM VirtualBox Disk Image >>>\n"; - const string SUN_VDI = "<<< Sun VirtualBox Disk Image >>>\n"; - const string INNOTEK_VDI = "<<< innotek VirtualBox Disk Image >>>\n"; - const string INNOTEK_OLD_VDI = "<<< InnoTek VirtualBox Disk Image >>>\n"; - const string DIC_VDI = "<<< DiscImageChef VirtualBox Disk Image >>>\n"; - - const uint MAX_CACHE_SIZE = 16777216; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; - const uint DEFAULT_BLOCK_SIZE = 1048576; - uint currentWritingPosition; - uint[] ibm; - ImageInfo imageInfo; - Stream imageStream; - - Dictionary<ulong, byte[]> sectorCache; - - VdiHeader vHdr; - FileStream writingStream; - - public Vdi() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "VirtualBox Disk Image"; - public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); - - public string Format => "VDI"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] vHdrB = new byte[Marshal.SizeOf(vHdr)]; - stream.Read(vHdrB, 0, Marshal.SizeOf(vHdr)); - vHdr = new VdiHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vHdr)); - Marshal.Copy(vHdrB, 0, headerPtr, Marshal.SizeOf(vHdr)); - vHdr = (VdiHeader)Marshal.PtrToStructure(headerPtr, typeof(VdiHeader)); - Marshal.FreeHGlobal(headerPtr); - - return vHdr.magic == VDI_MAGIC; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] vHdrB = new byte[Marshal.SizeOf(vHdr)]; - stream.Read(vHdrB, 0, Marshal.SizeOf(vHdr)); - vHdr = new VdiHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vHdr)); - Marshal.Copy(vHdrB, 0, headerPtr, Marshal.SizeOf(vHdr)); - vHdr = (VdiHeader)Marshal.PtrToStructure(headerPtr, typeof(VdiHeader)); - Marshal.FreeHGlobal(headerPtr); - - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.creator = {0}", vHdr.creator); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.magic = {0}", vHdr.magic); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.version = {0}.{1}", vHdr.majorVersion, - vHdr.minorVersion); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.headerSize = {0}", vHdr.headerSize); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.imageType = {0}", vHdr.imageType); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.imageFlags = {0}", vHdr.imageFlags); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.description = {0}", vHdr.comments); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.offsetBlocks = {0}", vHdr.offsetBlocks); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.offsetData = {0}", vHdr.offsetData); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.cylinders = {0}", vHdr.cylinders); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.heads = {0}", vHdr.heads); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.spt = {0}", vHdr.spt); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.sectorSize = {0}", vHdr.sectorSize); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.size = {0}", vHdr.size); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.blockSize = {0}", vHdr.blockSize); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.blockExtraData = {0}", vHdr.blockExtraData); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.blocks = {0}", vHdr.blocks); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.allocatedBlocks = {0}", vHdr.allocatedBlocks); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.uuid = {0}", vHdr.uuid); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.snapshotUuid = {0}", vHdr.snapshotUuid); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.linkUuid = {0}", vHdr.linkUuid); - DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.parentUuid = {0}", vHdr.parentUuid); - - if(vHdr.imageType != VdiImageType.Normal) - throw new - FeatureSupportedButNotImplementedImageException($"Support for image type {vHdr.imageType} not yet implemented"); - - DicConsole.DebugWriteLine("VirtualBox plugin", "Reading Image Block Map"); - stream.Seek(vHdr.offsetBlocks, SeekOrigin.Begin); - ibm = new uint[vHdr.blocks]; - byte[] ibmB = new byte[vHdr.blocks * 4]; - stream.Read(ibmB, 0, ibmB.Length); - for(int i = 0; i < ibm.Length; i++) ibm[i] = BitConverter.ToUInt32(ibmB, i * 4); - - sectorCache = new Dictionary<ulong, byte[]>(); - - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = vHdr.size / vHdr.sectorSize; - imageInfo.ImageSize = vHdr.size; - imageInfo.SectorSize = vHdr.sectorSize; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.Comments = vHdr.comments; - imageInfo.Version = $"{vHdr.majorVersion}.{vHdr.minorVersion}"; - - switch(vHdr.creator) - { - case SUN_VDI: - imageInfo.Application = "Sun VirtualBox"; - break; - case SUN_OLD_VDI: - imageInfo.Application = "Sun xVM"; - break; - case ORACLE_VDI: - imageInfo.Application = "Oracle VirtualBox"; - break; - case QEMUVDI: - imageInfo.Application = "QEMU"; - break; - case INNOTEK_VDI: - case INNOTEK_OLD_VDI: - imageInfo.Application = "innotek VirtualBox"; - break; - case DIC_VDI: - imageInfo.Application = "DiscImageChef"; - break; - } - - imageStream = stream; - - imageInfo.Cylinders = vHdr.cylinders; - imageInfo.Heads = vHdr.heads; - imageInfo.SectorsPerTrack = vHdr.spt; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - - ulong index = sectorAddress * vHdr.sectorSize / vHdr.blockSize; - ulong secOff = sectorAddress * vHdr.sectorSize % vHdr.blockSize; - - uint ibmOff = ibm[index]; - - if(ibmOff == VDI_EMPTY) return new byte[vHdr.sectorSize]; - - ulong imageOff = vHdr.offsetData + ibmOff * vHdr.blockSize; - - byte[] cluster = new byte[vHdr.blockSize]; - imageStream.Seek((long)imageOff, SeekOrigin.Begin); - imageStream.Read(cluster, 0, (int)vHdr.blockSize); - sector = new byte[vHdr.sectorSize]; - Array.Copy(cluster, (int)secOff, sector, 0, vHdr.sectorSize); - - if(sectorCache.Count > MAX_CACHED_SECTORS) sectorCache.Clear(); - - sectorCache.Add(sectorAddress, sector); - - return sector; - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), - $"Sector address {sectorAddress} not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), - $"Requested more sectors ({sectorAddress} + {length}) than available ({imageInfo.Sectors})"); - - MemoryStream ms = new MemoryStream(); - - for(uint i = 0; i < length; i++) - { - byte[] sector = ReadSector(sectorAddress + i); - ms.Write(sector, 0, sector.Length); - } - - return ms.ToArray(); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - // TODO: Add cluster size option - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".vdi"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - if(sectors * sectorSize / DEFAULT_BLOCK_SIZE > uint.MaxValue) - { - ErrorMessage = "Too many sectors for selected cluster size"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - uint ibmEntries = (uint)(sectors * sectorSize / DEFAULT_BLOCK_SIZE); - if(sectors * sectorSize % DEFAULT_BLOCK_SIZE > 0) ibmEntries++; - - uint headerSectors = 1 + ibmEntries * 4 / sectorSize; - if(ibmEntries * 4 % sectorSize != 0) headerSectors++; - ibm = new uint[ibmEntries]; - currentWritingPosition = headerSectors * sectorSize; - - vHdr = new VdiHeader - { - creator = DIC_VDI, - magic = VDI_MAGIC, - majorVersion = 1, - minorVersion = 1, - headerSize = Marshal.SizeOf(typeof(VdiHeader)) - 72, - imageType = VdiImageType.Normal, - offsetBlocks = sectorSize, - offsetData = currentWritingPosition, - sectorSize = sectorSize, - size = sectors * sectorSize, - blockSize = DEFAULT_BLOCK_SIZE, - blocks = ibmEntries, - uuid = Guid.NewGuid(), - snapshotUuid = Guid.NewGuid() - }; - - for(uint i = 0; i < ibmEntries; i++) ibm[i] = VDI_EMPTY; - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - ulong index = sectorAddress * vHdr.sectorSize / vHdr.blockSize; - ulong secOff = sectorAddress * vHdr.sectorSize % vHdr.blockSize; - - uint ibmOff = ibm[index]; - - if(ibmOff == VDI_EMPTY) - { - ibmOff = (currentWritingPosition - vHdr.offsetData) / vHdr.blockSize; - ibm[index] = ibmOff; - currentWritingPosition += vHdr.blockSize; - vHdr.allocatedBlocks++; - } - - ulong imageOff = vHdr.offsetData + ibmOff * vHdr.blockSize; - - writingStream.Seek((long)imageOff, SeekOrigin.Begin); - writingStream.Seek((long)secOff, SeekOrigin.Current); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - // TODO: This can be optimized - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - // Ignore empty sectors - if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; - - for(uint i = 0; i < length; i++) - { - byte[] tmp = new byte[imageInfo.SectorSize]; - Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); - if(!WriteSector(tmp, sectorAddress + i)) return false; - } - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(!string.IsNullOrEmpty(imageInfo.Comments)) - vHdr.comments = imageInfo.Comments.Length > 255 - ? imageInfo.Comments.Substring(0, 255) - : imageInfo.Comments; - - if(vHdr.cylinders == 0) - { - vHdr.cylinders = (uint)(imageInfo.Sectors / 16 / 63); - vHdr.heads = 16; - vHdr.spt = 63; - - while(vHdr.cylinders == 0) - { - vHdr.heads--; - - if(vHdr.heads == 0) - { - vHdr.spt--; - vHdr.heads = 16; - } - - vHdr.cylinders = (uint)(imageInfo.Sectors / vHdr.heads / vHdr.spt); - - if(vHdr.cylinders == 0 && vHdr.heads == 0 && vHdr.spt == 0) break; - } - } - - byte[] hdr = new byte[Marshal.SizeOf(typeof(VdiHeader))]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VdiHeader))); - Marshal.StructureToPtr(vHdr, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Seek(vHdr.offsetBlocks, SeekOrigin.Begin); - for(long i = 0; i < ibm.LongLength; i++) writingStream.Write(BitConverter.GetBytes(ibm[i]), 0, 4); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.Comments = metadata.Comments; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - vHdr.cylinders = cylinders; - vHdr.heads = heads; - vHdr.spt = sectorsPerTrack; - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - /// <summary> - /// VDI disk image header, little-endian - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VdiHeader - { - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] - public string creator; - /// <summary> - /// Magic, <see cref="Vdi.VDI_MAGIC" /> - /// </summary> - public uint magic; - /// <summary> - /// Version - /// </summary> - public ushort majorVersion; - public ushort minorVersion; - public int headerSize; - public VdiImageType imageType; - public VdiImageFlags imageFlags; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] - public string comments; - public uint offsetBlocks; - public uint offsetData; - public uint cylinders; - public uint heads; - public uint spt; - public uint sectorSize; - public uint unused; - public ulong size; - public uint blockSize; - public uint blockExtraData; - public uint blocks; - public uint allocatedBlocks; - public Guid uuid; - public Guid snapshotUuid; - public Guid linkUuid; - public Guid parentUuid; - } - - enum VdiImageType : uint - { - /// <summary> Normal dynamically growing base image file.</summary> - Normal = 1, - /// <summary>Preallocated base image file of a fixed size.</summary> - Fixed, - /// <summary>Dynamically growing image file for undo/commit changes support.</summary> - Undo, - /// <summary>Dynamically growing image file for differencing support.</summary> - Differential, - - /// <summary>First valid image type value.</summary> - First = Normal, - /// <summary>Last valid image type value.</summary> - Last = Differential - } - - enum VdiImageFlags : uint - { - /// <summary> - /// Fill new blocks with zeroes while expanding image file. Only valid for newly created images, never set - /// for opened existing images. - /// </summary> - ZeroExpand = 0x100 - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Constants.cs b/DiscImageChef.DiscImages/VDI/Constants.cs new file mode 100644 index 000000000..67884a9da --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Constants.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + const uint VDI_MAGIC = 0xBEDA107F; + const uint VDI_EMPTY = 0xFFFFFFFF; + + const string ORACLE_VDI = "<<< Oracle VM VirtualBox Disk Image >>>\n"; + const string QEMUVDI = "<<< QEMU VM Virtual Disk Image >>>\n"; + const string SUN_OLD_VDI = "<<< Sun xVM VirtualBox Disk Image >>>\n"; + const string SUN_VDI = "<<< Sun VirtualBox Disk Image >>>\n"; + const string INNOTEK_VDI = "<<< innotek VirtualBox Disk Image >>>\n"; + const string INNOTEK_OLD_VDI = "<<< InnoTek VirtualBox Disk Image >>>\n"; + const string DIC_VDI = "<<< DiscImageChef VirtualBox Disk Image >>>\n"; + + const uint MAX_CACHE_SIZE = 16777216; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512; + const uint DEFAULT_BLOCK_SIZE = 1048576; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Enums.cs b/DiscImageChef.DiscImages/VDI/Enums.cs new file mode 100644 index 000000000..891f6fd52 --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Enums.cs @@ -0,0 +1,63 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Enums.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains enumerations for VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + enum VdiImageType : uint + { + /// <summary> Normal dynamically growing base image file.</summary> + Normal = 1, + /// <summary>Preallocated base image file of a fixed size.</summary> + Fixed, + /// <summary>Dynamically growing image file for undo/commit changes support.</summary> + Undo, + /// <summary>Dynamically growing image file for differencing support.</summary> + Differential, + + /// <summary>First valid image type value.</summary> + First = Normal, + /// <summary>Last valid image type value.</summary> + Last = Differential + } + + enum VdiImageFlags : uint + { + /// <summary> + /// Fill new blocks with zeroes while expanding image file. Only valid for newly created images, never set + /// for opened existing images. + /// </summary> + ZeroExpand = 0x100 + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Identify.cs b/DiscImageChef.DiscImages/VDI/Identify.cs new file mode 100644 index 000000000..b891606fc --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Identify.cs @@ -0,0 +1,60 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] vHdrB = new byte[Marshal.SizeOf(vHdr)]; + stream.Read(vHdrB, 0, Marshal.SizeOf(vHdr)); + vHdr = new VdiHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vHdr)); + Marshal.Copy(vHdrB, 0, headerPtr, Marshal.SizeOf(vHdr)); + vHdr = (VdiHeader)Marshal.PtrToStructure(headerPtr, typeof(VdiHeader)); + Marshal.FreeHGlobal(headerPtr); + + return vHdr.magic == VDI_MAGIC; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Properties.cs b/DiscImageChef.DiscImages/VDI/Properties.cs new file mode 100644 index 000000000..dceb3ce8d --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Properties.cs @@ -0,0 +1,79 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + public ImageInfo Info => imageInfo; + + public string Name => "VirtualBox Disk Image"; + public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); + + public string Format => "VDI"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + // TODO: Add cluster size option + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".vdi"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Read.cs b/DiscImageChef.DiscImages/VDI/Read.cs new file mode 100644 index 000000000..991c27266 --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Read.cs @@ -0,0 +1,193 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] vHdrB = new byte[Marshal.SizeOf(vHdr)]; + stream.Read(vHdrB, 0, Marshal.SizeOf(vHdr)); + vHdr = new VdiHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vHdr)); + Marshal.Copy(vHdrB, 0, headerPtr, Marshal.SizeOf(vHdr)); + vHdr = (VdiHeader)Marshal.PtrToStructure(headerPtr, typeof(VdiHeader)); + Marshal.FreeHGlobal(headerPtr); + + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.creator = {0}", vHdr.creator); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.magic = {0}", vHdr.magic); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.version = {0}.{1}", vHdr.majorVersion, + vHdr.minorVersion); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.headerSize = {0}", vHdr.headerSize); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.imageType = {0}", vHdr.imageType); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.imageFlags = {0}", vHdr.imageFlags); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.description = {0}", vHdr.comments); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.offsetBlocks = {0}", vHdr.offsetBlocks); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.offsetData = {0}", vHdr.offsetData); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.cylinders = {0}", vHdr.cylinders); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.heads = {0}", vHdr.heads); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.spt = {0}", vHdr.spt); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.sectorSize = {0}", vHdr.sectorSize); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.size = {0}", vHdr.size); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.blockSize = {0}", vHdr.blockSize); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.blockExtraData = {0}", vHdr.blockExtraData); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.blocks = {0}", vHdr.blocks); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.allocatedBlocks = {0}", vHdr.allocatedBlocks); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.uuid = {0}", vHdr.uuid); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.snapshotUuid = {0}", vHdr.snapshotUuid); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.linkUuid = {0}", vHdr.linkUuid); + DicConsole.DebugWriteLine("VirtualBox plugin", "vHdr.parentUuid = {0}", vHdr.parentUuid); + + if(vHdr.imageType != VdiImageType.Normal) + throw new + FeatureSupportedButNotImplementedImageException($"Support for image type {vHdr.imageType} not yet implemented"); + + DicConsole.DebugWriteLine("VirtualBox plugin", "Reading Image Block Map"); + stream.Seek(vHdr.offsetBlocks, SeekOrigin.Begin); + ibm = new uint[vHdr.blocks]; + byte[] ibmB = new byte[vHdr.blocks * 4]; + stream.Read(ibmB, 0, ibmB.Length); + for(int i = 0; i < ibm.Length; i++) ibm[i] = BitConverter.ToUInt32(ibmB, i * 4); + + sectorCache = new Dictionary<ulong, byte[]>(); + + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = vHdr.size / vHdr.sectorSize; + imageInfo.ImageSize = vHdr.size; + imageInfo.SectorSize = vHdr.sectorSize; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.Comments = vHdr.comments; + imageInfo.Version = $"{vHdr.majorVersion}.{vHdr.minorVersion}"; + + switch(vHdr.creator) + { + case SUN_VDI: + imageInfo.Application = "Sun VirtualBox"; + break; + case SUN_OLD_VDI: + imageInfo.Application = "Sun xVM"; + break; + case ORACLE_VDI: + imageInfo.Application = "Oracle VirtualBox"; + break; + case QEMUVDI: + imageInfo.Application = "QEMU"; + break; + case INNOTEK_VDI: + case INNOTEK_OLD_VDI: + imageInfo.Application = "innotek VirtualBox"; + break; + case DIC_VDI: + imageInfo.Application = "DiscImageChef"; + break; + } + + imageStream = stream; + + imageInfo.Cylinders = vHdr.cylinders; + imageInfo.Heads = vHdr.heads; + imageInfo.SectorsPerTrack = vHdr.spt; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; + + ulong index = sectorAddress * vHdr.sectorSize / vHdr.blockSize; + ulong secOff = sectorAddress * vHdr.sectorSize % vHdr.blockSize; + + uint ibmOff = ibm[index]; + + if(ibmOff == VDI_EMPTY) return new byte[vHdr.sectorSize]; + + ulong imageOff = vHdr.offsetData + ibmOff * vHdr.blockSize; + + byte[] cluster = new byte[vHdr.blockSize]; + imageStream.Seek((long)imageOff, SeekOrigin.Begin); + imageStream.Read(cluster, 0, (int)vHdr.blockSize); + sector = new byte[vHdr.sectorSize]; + Array.Copy(cluster, (int)secOff, sector, 0, vHdr.sectorSize); + + if(sectorCache.Count > MAX_CACHED_SECTORS) sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), + $"Sector address {sectorAddress} not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({sectorAddress} + {length}) than available ({imageInfo.Sectors})"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Structs.cs b/DiscImageChef.DiscImages/VDI/Structs.cs new file mode 100644 index 000000000..b1155f072 --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Structs.cs @@ -0,0 +1,80 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + /// <summary> + /// VDI disk image header, little-endian + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VdiHeader + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string creator; + /// <summary> + /// Magic, <see cref="Vdi.VDI_MAGIC" /> + /// </summary> + public uint magic; + /// <summary> + /// Version + /// </summary> + public ushort majorVersion; + public ushort minorVersion; + public int headerSize; + public VdiImageType imageType; + public VdiImageFlags imageFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string comments; + public uint offsetBlocks; + public uint offsetData; + public uint cylinders; + public uint heads; + public uint spt; + public uint sectorSize; + public uint unused; + public ulong size; + public uint blockSize; + public uint blockExtraData; + public uint blocks; + public uint allocatedBlocks; + public Guid uuid; + public Guid snapshotUuid; + public Guid linkUuid; + public Guid parentUuid; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Unsupported.cs b/DiscImageChef.DiscImages/VDI/Unsupported.cs new file mode 100644 index 000000000..b1ec6f8d9 --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/VDI.cs b/DiscImageChef.DiscImages/VDI/VDI.cs new file mode 100644 index 000000000..222099d2b --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/VDI.cs @@ -0,0 +1,81 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : VDI.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // TODO: Support version 0 + // TODO: Support fixed images + // TODO: Support version 1.2 geometry + public partial class Vdi : IWritableImage + { + uint currentWritingPosition; + uint[] ibm; + ImageInfo imageInfo; + Stream imageStream; + Dictionary<ulong, byte[]> sectorCache; + VdiHeader vHdr; + FileStream writingStream; + + public Vdi() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VDI/Write.cs b/DiscImageChef.DiscImages/VDI/Write.cs new file mode 100644 index 000000000..902483da8 --- /dev/null +++ b/DiscImageChef.DiscImages/VDI/Write.cs @@ -0,0 +1,308 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes VirtualBox disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Vdi + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + if(sectors * sectorSize / DEFAULT_BLOCK_SIZE > uint.MaxValue) + { + ErrorMessage = "Too many sectors for selected cluster size"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + uint ibmEntries = (uint)(sectors * sectorSize / DEFAULT_BLOCK_SIZE); + if(sectors * sectorSize % DEFAULT_BLOCK_SIZE > 0) ibmEntries++; + + uint headerSectors = 1 + ibmEntries * 4 / sectorSize; + if(ibmEntries * 4 % sectorSize != 0) headerSectors++; + ibm = new uint[ibmEntries]; + currentWritingPosition = headerSectors * sectorSize; + + vHdr = new VdiHeader + { + creator = DIC_VDI, + magic = VDI_MAGIC, + majorVersion = 1, + minorVersion = 1, + headerSize = Marshal.SizeOf(typeof(VdiHeader)) - 72, + imageType = VdiImageType.Normal, + offsetBlocks = sectorSize, + offsetData = currentWritingPosition, + sectorSize = sectorSize, + size = sectors * sectorSize, + blockSize = DEFAULT_BLOCK_SIZE, + blocks = ibmEntries, + uuid = Guid.NewGuid(), + snapshotUuid = Guid.NewGuid() + }; + + for(uint i = 0; i < ibmEntries; i++) ibm[i] = VDI_EMPTY; + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + ulong index = sectorAddress * vHdr.sectorSize / vHdr.blockSize; + ulong secOff = sectorAddress * vHdr.sectorSize % vHdr.blockSize; + + uint ibmOff = ibm[index]; + + if(ibmOff == VDI_EMPTY) + { + ibmOff = (currentWritingPosition - vHdr.offsetData) / vHdr.blockSize; + ibm[index] = ibmOff; + currentWritingPosition += vHdr.blockSize; + vHdr.allocatedBlocks++; + } + + ulong imageOff = vHdr.offsetData + ibmOff * vHdr.blockSize; + + writingStream.Seek((long)imageOff, SeekOrigin.Begin); + writingStream.Seek((long)secOff, SeekOrigin.Current); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + // TODO: This can be optimized + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + // Ignore empty sectors + if(ArrayHelpers.ArrayIsNullOrEmpty(data)) return true; + + for(uint i = 0; i < length; i++) + { + byte[] tmp = new byte[imageInfo.SectorSize]; + Array.Copy(data, i * imageInfo.SectorSize, tmp, 0, imageInfo.SectorSize); + if(!WriteSector(tmp, sectorAddress + i)) return false; + } + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(!string.IsNullOrEmpty(imageInfo.Comments)) + vHdr.comments = imageInfo.Comments.Length > 255 + ? imageInfo.Comments.Substring(0, 255) + : imageInfo.Comments; + + if(vHdr.cylinders == 0) + { + vHdr.cylinders = (uint)(imageInfo.Sectors / 16 / 63); + vHdr.heads = 16; + vHdr.spt = 63; + + while(vHdr.cylinders == 0) + { + vHdr.heads--; + + if(vHdr.heads == 0) + { + vHdr.spt--; + vHdr.heads = 16; + } + + vHdr.cylinders = (uint)(imageInfo.Sectors / vHdr.heads / vHdr.spt); + + if(vHdr.cylinders == 0 && vHdr.heads == 0 && vHdr.spt == 0) break; + } + } + + byte[] hdr = new byte[Marshal.SizeOf(typeof(VdiHeader))]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VdiHeader))); + Marshal.StructureToPtr(vHdr, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Seek(vHdr.offsetBlocks, SeekOrigin.Begin); + for(long i = 0; i < ibm.LongLength; i++) writingStream.Write(BitConverter.GetBytes(ibm[i]), 0, 4); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.Comments = metadata.Comments; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + vHdr.cylinders = cylinders; + vHdr.heads = heads; + vHdr.spt = sectorsPerTrack; + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/Constants.cs b/DiscImageChef.DiscImages/VHD/Constants.cs new file mode 100644 index 000000000..3dcd1633f --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/Constants.cs @@ -0,0 +1,173 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Vhd + { + /// <summary> + /// File magic number, "conectix" + /// </summary> + const ulong IMAGE_COOKIE = 0x636F6E6563746978; + /// <summary> + /// Dynamic disk header magic, "cxsparse" + /// </summary> + const ulong DYNAMIC_COOKIE = 0x6378737061727365; + + /// <summary> + /// Disk image is candidate for deletion on shutdown + /// </summary> + const uint FEATURES_TEMPORARY = 0x00000001; + /// <summary> + /// Unknown, set from Virtual PC for Mac 7 onwards + /// </summary> + const uint FEATURES_RESERVED = 0x00000002; + /// <summary> + /// Unknown + /// </summary> + const uint FEATURES_UNKNOWN = 0x00000100; + + /// <summary> + /// Only known version + /// </summary> + const uint VERSION1 = 0x00010000; + + /// <summary> + /// Created by Virtual PC, "vpc " + /// </summary> + const uint CREATOR_VIRTUAL_PC = 0x76706320; + /// <summary> + /// Created by Virtual Server, "vs " + /// </summary> + const uint CREATOR_VIRTUAL_SERVER = 0x76732020; + /// <summary> + /// Created by QEMU, "qemu" + /// </summary> + const uint CREATOR_QEMU = 0x71656D75; + /// <summary> + /// Created by VirtualBox, "vbox" + /// </summary> + const uint CREATOR_VIRTUAL_BOX = 0x76626F78; + /// <summary> + /// Created by DiscImageChef, "dic " + /// </summary> + const uint CREATOR_DISCIMAGECHEF = 0x64696320; + + /// <summary> + /// Disk image created by Virtual Server 2004 + /// </summary> + const uint VERSION_VIRTUAL_SERVER2004 = 0x00010000; + /// <summary> + /// Disk image created by Virtual PC 2004 + /// </summary> + const uint VERSION_VIRTUAL_PC2004 = 0x00050000; + /// <summary> + /// Disk image created by Virtual PC 2007 + /// </summary> + const uint VERSION_VIRTUAL_PC2007 = 0x00050003; + /// <summary> + /// Disk image created by Virtual PC for Mac 5, 6 or 7 + /// </summary> + const uint VERSION_VIRTUAL_PC_MAC = 0x00040000; + + /// <summary> + /// Disk image created in Windows, "Wi2k" + /// </summary> + const uint CREATOR_WINDOWS = 0x5769326B; + /// <summary> + /// Disk image created in Macintosh, "Mac " + /// </summary> + const uint CREATOR_MACINTOSH = 0x4D616320; + /// <summary> + /// Seen in Virtual PC for Mac for dynamic and fixed images + /// </summary> + const uint CREATOR_MACINTOSH_OLD = 0x00000000; + + /// <summary> + /// Disk image type is none, useless? + /// </summary> + const uint TYPE_NONE = 0; + /// <summary> + /// Deprecated disk image type + /// </summary> + const uint TYPE_DEPRECATED1 = 1; + /// <summary> + /// Fixed disk image type + /// </summary> + const uint TYPE_FIXED = 2; + /// <summary> + /// Dynamic disk image type + /// </summary> + const uint TYPE_DYNAMIC = 3; + /// <summary> + /// Differencing disk image type + /// </summary> + const uint TYPE_DIFFERENCING = 4; + /// <summary> + /// Deprecated disk image type + /// </summary> + const uint TYPE_DEPRECATED2 = 5; + /// <summary> + /// Deprecated disk image type + /// </summary> + const uint TYPE_DEPRECATED3 = 6; + + /// <summary> + /// Means platform locator is unused + /// </summary> + const uint PLATFORM_CODE_UNUSED = 0x00000000; + /// <summary> + /// Stores a relative path string for Windows, unknown locale used, deprecated, "Wi2r" + /// </summary> + const uint PLATFORM_CODE_WINDOWS_RELATIVE = 0x57693272; + /// <summary> + /// Stores an absolute path string for Windows, unknown locale used, deprecated, "Wi2k" + /// </summary> + const uint PLATFORM_CODE_WINDOWS_ABSOLUTE = 0x5769326B; + /// <summary> + /// Stores a relative path string for Windows in UTF-16, "W2ru" + /// </summary> + const uint PLATFORM_CODE_WINDOWS_RELATIVE_U = 0x57327275; + /// <summary> + /// Stores an absolute path string for Windows in UTF-16, "W2ku" + /// </summary> + const uint PLATFORM_CODE_WINDOWS_ABSOLUTE_U = 0x57326B75; + /// <summary> + /// Stores a Mac OS alias as a blob, "Mac " + /// </summary> + const uint PLATFORM_CODE_MACINTOSH_ALIAS = 0x4D616320; + /// <summary> + /// Stores a Mac OS X URI (RFC-2396) absolute path in UTF-8, "MacX" + /// </summary> + const uint PLATFORM_CODE_MACINTOSH_URI = 0x4D616358; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/Helpers.cs b/DiscImageChef.DiscImages/VHD/Helpers.cs new file mode 100644 index 000000000..67c2a69ee --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/Helpers.cs @@ -0,0 +1,47 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Linq; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhd + { + static uint VhdChecksum(IEnumerable<byte> data) + { + uint checksum = data.Aggregate<byte, uint>(0, (current, b) => current + b); + + return ~checksum; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/Identify.cs b/DiscImageChef.DiscImages/VHD/Identify.cs new file mode 100644 index 000000000..fae6dd3bd --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/Identify.cs @@ -0,0 +1,62 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhd + { + public bool Identify(IFilter imageFilter) + { + Stream imageStream = imageFilter.GetDataForkStream(); + + byte[] headerCookieBytes = new byte[8]; + byte[] footerCookieBytes = new byte[8]; + + if(imageStream.Length % 2 == 0) imageStream.Seek(-512, SeekOrigin.End); + else imageStream.Seek(-511, SeekOrigin.End); + + imageStream.Read(footerCookieBytes, 0, 8); + imageStream.Seek(0, SeekOrigin.Begin); + imageStream.Read(headerCookieBytes, 0, 8); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + ulong headerCookie = BigEndianBitConverter.ToUInt64(headerCookieBytes, 0); + ulong footerCookie = BigEndianBitConverter.ToUInt64(footerCookieBytes, 0); + + return headerCookie == IMAGE_COOKIE || footerCookie == IMAGE_COOKIE; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/Properties.cs b/DiscImageChef.DiscImages/VHD/Properties.cs new file mode 100644 index 000000000..d72d1365a --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/Properties.cs @@ -0,0 +1,91 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhd + { + public ImageInfo Info => imageInfo; + + public string Name => "VirtualPC"; + public Guid Id => new Guid("8014d88f-64cd-4484-9441-7635c632958a"); + + public string Format + { + get + { + switch(thisFooter.DiskType) + { + case TYPE_FIXED: return "Virtual PC fixed size disk image"; + case TYPE_DYNAMIC: return "Virtual PC dynamic size disk image"; + case TYPE_DIFFERENCING: return "Virtual PC differencing disk image"; + default: return "Virtual PC disk image"; + } + } + } + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + // TODO: Support dynamic images + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".vhd"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD.cs b/DiscImageChef.DiscImages/VHD/Read.cs similarity index 58% rename from DiscImageChef.DiscImages/VHD.cs rename to DiscImageChef.DiscImages/VHD/Read.cs index da84f26bc..105df5c7c 100644 --- a/DiscImageChef.DiscImages/VHD.cs +++ b/DiscImageChef.DiscImages/VHD/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : VHD.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Connectix and Microsoft Virtual PC disk images. +// Reads Connectix and Microsoft Virtual PC disk images. // // --[ License ] -------------------------------------------------------------- // @@ -31,9 +31,7 @@ // ****************************************************************************/ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Text; using DiscImageChef.Checksums; @@ -41,249 +39,12 @@ using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Interop; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; -using PlatformID = DiscImageChef.CommonTypes.Interop.PlatformID; -using Version = System.Version; namespace DiscImageChef.DiscImages { - /// <inheritdoc /> - /// <summary> - /// Supports Connectix/Microsoft Virtual PC hard disk image format - /// Until Virtual PC 5 there existed no format, and the hard disk image was - /// merely a sector by sector (RAW) image with a resource fork giving - /// information to Virtual PC itself. - /// </summary> - public class Vhd : IWritableImage + public partial class Vhd { - /// <summary> - /// File magic number, "conectix" - /// </summary> - const ulong IMAGE_COOKIE = 0x636F6E6563746978; - /// <summary> - /// Dynamic disk header magic, "cxsparse" - /// </summary> - const ulong DYNAMIC_COOKIE = 0x6378737061727365; - - /// <summary> - /// Disk image is candidate for deletion on shutdown - /// </summary> - const uint FEATURES_TEMPORARY = 0x00000001; - /// <summary> - /// Unknown, set from Virtual PC for Mac 7 onwards - /// </summary> - const uint FEATURES_RESERVED = 0x00000002; - /// <summary> - /// Unknown - /// </summary> - const uint FEATURES_UNKNOWN = 0x00000100; - - /// <summary> - /// Only known version - /// </summary> - const uint VERSION1 = 0x00010000; - - /// <summary> - /// Created by Virtual PC, "vpc " - /// </summary> - const uint CREATOR_VIRTUAL_PC = 0x76706320; - /// <summary> - /// Created by Virtual Server, "vs " - /// </summary> - const uint CREATOR_VIRTUAL_SERVER = 0x76732020; - /// <summary> - /// Created by QEMU, "qemu" - /// </summary> - const uint CREATOR_QEMU = 0x71656D75; - /// <summary> - /// Created by VirtualBox, "vbox" - /// </summary> - const uint CREATOR_VIRTUAL_BOX = 0x76626F78; - /// <summary> - /// Created by DiscImageChef, "dic " - /// </summary> - const uint CREATOR_DISCIMAGECHEF = 0x64696320; - - /// <summary> - /// Disk image created by Virtual Server 2004 - /// </summary> - const uint VERSION_VIRTUAL_SERVER2004 = 0x00010000; - /// <summary> - /// Disk image created by Virtual PC 2004 - /// </summary> - const uint VERSION_VIRTUAL_PC2004 = 0x00050000; - /// <summary> - /// Disk image created by Virtual PC 2007 - /// </summary> - const uint VERSION_VIRTUAL_PC2007 = 0x00050003; - /// <summary> - /// Disk image created by Virtual PC for Mac 5, 6 or 7 - /// </summary> - const uint VERSION_VIRTUAL_PC_MAC = 0x00040000; - - /// <summary> - /// Disk image created in Windows, "Wi2k" - /// </summary> - const uint CREATOR_WINDOWS = 0x5769326B; - /// <summary> - /// Disk image created in Macintosh, "Mac " - /// </summary> - const uint CREATOR_MACINTOSH = 0x4D616320; - /// <summary> - /// Seen in Virtual PC for Mac for dynamic and fixed images - /// </summary> - const uint CREATOR_MACINTOSH_OLD = 0x00000000; - - /// <summary> - /// Disk image type is none, useless? - /// </summary> - const uint TYPE_NONE = 0; - /// <summary> - /// Deprecated disk image type - /// </summary> - const uint TYPE_DEPRECATED1 = 1; - /// <summary> - /// Fixed disk image type - /// </summary> - const uint TYPE_FIXED = 2; - /// <summary> - /// Dynamic disk image type - /// </summary> - const uint TYPE_DYNAMIC = 3; - /// <summary> - /// Differencing disk image type - /// </summary> - const uint TYPE_DIFFERENCING = 4; - /// <summary> - /// Deprecated disk image type - /// </summary> - const uint TYPE_DEPRECATED2 = 5; - /// <summary> - /// Deprecated disk image type - /// </summary> - const uint TYPE_DEPRECATED3 = 6; - - /// <summary> - /// Means platform locator is unused - /// </summary> - const uint PLATFORM_CODE_UNUSED = 0x00000000; - /// <summary> - /// Stores a relative path string for Windows, unknown locale used, deprecated, "Wi2r" - /// </summary> - const uint PLATFORM_CODE_WINDOWS_RELATIVE = 0x57693272; - /// <summary> - /// Stores an absolute path string for Windows, unknown locale used, deprecated, "Wi2k" - /// </summary> - const uint PLATFORM_CODE_WINDOWS_ABSOLUTE = 0x5769326B; - /// <summary> - /// Stores a relative path string for Windows in UTF-16, "W2ru" - /// </summary> - const uint PLATFORM_CODE_WINDOWS_RELATIVE_U = 0x57327275; - /// <summary> - /// Stores an absolute path string for Windows in UTF-16, "W2ku" - /// </summary> - const uint PLATFORM_CODE_WINDOWS_ABSOLUTE_U = 0x57326B75; - /// <summary> - /// Stores a Mac OS alias as a blob, "Mac " - /// </summary> - const uint PLATFORM_CODE_MACINTOSH_ALIAS = 0x4D616320; - /// <summary> - /// Stores a Mac OS X URI (RFC-2396) absolute path in UTF-8, "MacX" - /// </summary> - const uint PLATFORM_CODE_MACINTOSH_URI = 0x4D616358; - uint bitmapSize; - uint[] blockAllocationTable; - ImageInfo imageInfo; - byte[][] locatorEntriesData; - DateTime parentDateTime; - IMediaImage parentImage; - DateTime thisDateTime; - DynamicDiskHeader thisDynamic; - IFilter thisFilter; - - HardDiskFooter thisFooter; - FileStream writingStream; - - public Vhd() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "VirtualPC"; - public Guid Id => new Guid("8014d88f-64cd-4484-9441-7635c632958a"); - - public string Format - { - get - { - switch(thisFooter.DiskType) - { - case TYPE_FIXED: return "Virtual PC fixed size disk image"; - case TYPE_DYNAMIC: return "Virtual PC dynamic size disk image"; - case TYPE_DIFFERENCING: return "Virtual PC differencing disk image"; - default: return "Virtual PC disk image"; - } - } - } - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream imageStream = imageFilter.GetDataForkStream(); - - byte[] headerCookieBytes = new byte[8]; - byte[] footerCookieBytes = new byte[8]; - - if(imageStream.Length % 2 == 0) imageStream.Seek(-512, SeekOrigin.End); - else imageStream.Seek(-511, SeekOrigin.End); - - imageStream.Read(footerCookieBytes, 0, 8); - imageStream.Seek(0, SeekOrigin.Begin); - imageStream.Read(headerCookieBytes, 0, 8); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - ulong headerCookie = BigEndianBitConverter.ToUInt64(headerCookieBytes, 0); - ulong footerCookie = BigEndianBitConverter.ToUInt64(footerCookieBytes, 0); - - return headerCookie == IMAGE_COOKIE || footerCookie == IMAGE_COOKIE; - } - public bool Open(IFilter imageFilter) { Stream imageStream = imageFilter.GetDataForkStream(); @@ -996,535 +757,5 @@ namespace DiscImageChef.DiscImages } } } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - // TODO: Support dynamic images - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".vhd"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(0 + sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - // TODO: Implement dynamic - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(0 + sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - Version thisVersion = GetType().Assembly.GetName().Version; - - if(imageInfo.Cylinders == 0) - { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - while(imageInfo.Cylinders == 0) - { - imageInfo.Heads--; - - if(imageInfo.Heads == 0) - { - imageInfo.SectorsPerTrack--; - imageInfo.Heads = 16; - } - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); - - if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; - } - } - - HardDiskFooter footer = new HardDiskFooter - { - Cookie = IMAGE_COOKIE, - Features = FEATURES_RESERVED, - Version = VERSION1, - Timestamp = - (uint)(DateTime.Now - new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds, - CreatorApplication = CREATOR_DISCIMAGECHEF, - CreatorVersion = - (uint)(((thisVersion.Major & 0xFF) << 24) + ((thisVersion.Minor & 0xFF) << 16) + - ((thisVersion.Build & 0xFF) << 8) + (thisVersion.Revision & 0xFF)), - CreatorHostOs = DetectOS.GetRealPlatformID() == PlatformID.MacOSX ? CREATOR_MACINTOSH : CREATOR_WINDOWS, - DiskType = TYPE_FIXED, - UniqueId = Guid.NewGuid(), - DiskGeometry = - ((imageInfo.Cylinders & 0xFFFF) << 16) + ((imageInfo.Heads & 0xFF) << 8) + - (imageInfo.SectorsPerTrack & 0xFF), - OriginalSize = imageInfo.Sectors * 512, - CurrentSize = imageInfo.Sectors * 512 - }; - footer.Offset = footer.DiskType == TYPE_FIXED ? ulong.MaxValue : 512; - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - byte[] footerBytes = new byte[512]; - Array.Copy(BigEndianBitConverter.GetBytes(footer.Cookie), 0, footerBytes, 0x00, 8); - Array.Copy(BigEndianBitConverter.GetBytes(footer.Features), 0, footerBytes, 0x08, 4); - Array.Copy(BigEndianBitConverter.GetBytes(footer.Version), 0, footerBytes, 0x0C, 4); - Array.Copy(BigEndianBitConverter.GetBytes(footer.Offset), 0, footerBytes, 0x10, 8); - Array.Copy(BigEndianBitConverter.GetBytes(footer.Timestamp), 0, footerBytes, 0x18, 4); - Array.Copy(BigEndianBitConverter.GetBytes(footer.CreatorApplication), 0, footerBytes, 0x1C, 4); - Array.Copy(BigEndianBitConverter.GetBytes(footer.CreatorVersion), 0, footerBytes, 0x20, 4); - Array.Copy(BigEndianBitConverter.GetBytes(footer.CreatorHostOs), 0, footerBytes, 0x24, 4); - Array.Copy(BigEndianBitConverter.GetBytes(footer.OriginalSize), 0, footerBytes, 0x28, 8); - Array.Copy(BigEndianBitConverter.GetBytes(footer.CurrentSize), 0, footerBytes, 0x30, 8); - Array.Copy(BigEndianBitConverter.GetBytes(footer.DiskGeometry), 0, footerBytes, 0x38, 4); - Array.Copy(BigEndianBitConverter.GetBytes(footer.DiskType), 0, footerBytes, 0x3C, 4); - Array.Copy(footer.UniqueId.ToByteArray(), 0, footerBytes, 0x44, 4); - - footer.Checksum = VhdChecksum(footerBytes); - Array.Copy(BigEndianBitConverter.GetBytes(footer.Checksum), 0, footerBytes, 0x40, 4); - - writingStream.Seek((long)(footer.DiskType == TYPE_FIXED ? footer.OriginalSize : 0), SeekOrigin.Begin); - writingStream.Write(footerBytes, 0, 512); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > 0xFFFF) - { - ErrorMessage = "Too many cylinders."; - return false; - } - - if(heads > 0xFF) - { - ErrorMessage = "Too many heads."; - return false; - } - - if(sectorsPerTrack > 0xFF) - { - ErrorMessage = "Too many sectors per track."; - return false; - } - - imageInfo.SectorsPerTrack = sectorsPerTrack; - imageInfo.Heads = heads; - imageInfo.Cylinders = cylinders; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - static uint VhdChecksum(IEnumerable<byte> data) - { - uint checksum = data.Aggregate<byte, uint>(0, (current, b) => current + b); - - return ~checksum; - } - - struct HardDiskFooter - { - /// <summary> - /// Offset 0x00, File magic number, <see cref="Vhd.IMAGE_COOKIE" /> - /// </summary> - public ulong Cookie; - /// <summary> - /// Offset 0x08, Specific feature support - /// </summary> - public uint Features; - /// <summary> - /// Offset 0x0C, File format version - /// </summary> - public uint Version; - /// <summary> - /// Offset 0x10, Offset from beginning of file to next structure - /// </summary> - public ulong Offset; - /// <summary> - /// Offset 0x18, Creation date seconds since 2000/01/01 00:00:00 UTC - /// </summary> - public uint Timestamp; - /// <summary> - /// Offset 0x1C, Application that created this disk image - /// </summary> - public uint CreatorApplication; - /// <summary> - /// Offset 0x20, Version of the application that created this disk image - /// </summary> - public uint CreatorVersion; - /// <summary> - /// Offset 0x24, Host operating system of the application that created this disk image - /// </summary> - public uint CreatorHostOs; - /// <summary> - /// Offset 0x28, Original hard disk size, in bytes - /// </summary> - public ulong OriginalSize; - /// <summary> - /// Offset 0x30, Current hard disk size, in bytes - /// </summary> - public ulong CurrentSize; - /// <summary> - /// Offset 0x38, CHS geometry - /// Cylinder mask = 0xFFFF0000 - /// Heads mask = 0x0000FF00 - /// Sectors mask = 0x000000FF - /// </summary> - public uint DiskGeometry; - /// <summary> - /// Offset 0x3C, Disk image type - /// </summary> - public uint DiskType; - /// <summary> - /// Offset 0x40, Checksum for this structure - /// </summary> - public uint Checksum; - /// <summary> - /// Offset 0x44, UUID, used to associate parent with differencing disk images - /// </summary> - public Guid UniqueId; - /// <summary> - /// Offset 0x54, If set, system is saved, so compaction and expansion cannot be performed - /// </summary> - public byte SavedState; - /// <summary> - /// Offset 0x55, 427 bytes reserved, should contain zeros. - /// </summary> - public byte[] Reserved; - } - - struct ParentLocatorEntry - { - /// <summary> - /// Offset 0x00, Describes the platform specific type this entry belongs to - /// </summary> - public uint PlatformCode; - /// <summary> - /// Offset 0x04, Describes the number of 512 bytes sectors used by this entry - /// </summary> - public uint PlatformDataSpace; - /// <summary> - /// Offset 0x08, Describes this entry's size in bytes - /// </summary> - public uint PlatformDataLength; - /// <summary> - /// Offset 0x0c, Reserved - /// </summary> - public uint Reserved; - /// <summary> - /// Offset 0x10, Offset on disk image this entry resides on - /// </summary> - public ulong PlatformDataOffset; - } - - struct DynamicDiskHeader - { - /// <summary> - /// Offset 0x00, Header magic, <see cref="Vhd.DYNAMIC_COOKIE" /> - /// </summary> - public ulong Cookie; - /// <summary> - /// Offset 0x08, Offset to next structure on disk image. - /// Currently unused, 0xFFFFFFFF - /// </summary> - public ulong DataOffset; - /// <summary> - /// Offset 0x10, Offset of the Block Allocation Table (BAT) - /// </summary> - public ulong TableOffset; - /// <summary> - /// Offset 0x18, Version of this header - /// </summary> - public uint HeaderVersion; - /// <summary> - /// Offset 0x1C, Maximum entries present in the BAT - /// </summary> - public uint MaxTableEntries; - /// <summary> - /// Offset 0x20, Size of a block in bytes - /// Should always be a power of two of 512 - /// </summary> - public uint BlockSize; - /// <summary> - /// Offset 0x24, Checksum of this header - /// </summary> - public uint Checksum; - /// <summary> - /// Offset 0x28, UUID of parent disk image for differencing type - /// </summary> - public Guid ParentId; - /// <summary> - /// Offset 0x38, Timestamp of parent disk image - /// </summary> - public uint ParentTimestamp; - /// <summary> - /// Offset 0x3C, Reserved - /// </summary> - public uint Reserved; - /// <summary> - /// Offset 0x40, 512 bytes UTF-16 of parent disk image filename - /// </summary> - public string ParentName; - /// <summary> - /// Offset 0x240, Parent disk image locator entry, <see cref="ParentLocatorEntry" /> - /// </summary> - public ParentLocatorEntry[] LocatorEntries; - /// <summary> - /// Offset 0x300, 256 reserved bytes - /// </summary> - public byte[] Reserved2; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct BatSector - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] - public uint[] blockPointer; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/Structs.cs b/DiscImageChef.DiscImages/VHD/Structs.cs new file mode 100644 index 000000000..2e533a648 --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/Structs.cs @@ -0,0 +1,200 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhd + { + struct HardDiskFooter + { + /// <summary> + /// Offset 0x00, File magic number, <see cref="Vhd.IMAGE_COOKIE" /> + /// </summary> + public ulong Cookie; + /// <summary> + /// Offset 0x08, Specific feature support + /// </summary> + public uint Features; + /// <summary> + /// Offset 0x0C, File format version + /// </summary> + public uint Version; + /// <summary> + /// Offset 0x10, Offset from beginning of file to next structure + /// </summary> + public ulong Offset; + /// <summary> + /// Offset 0x18, Creation date seconds since 2000/01/01 00:00:00 UTC + /// </summary> + public uint Timestamp; + /// <summary> + /// Offset 0x1C, Application that created this disk image + /// </summary> + public uint CreatorApplication; + /// <summary> + /// Offset 0x20, Version of the application that created this disk image + /// </summary> + public uint CreatorVersion; + /// <summary> + /// Offset 0x24, Host operating system of the application that created this disk image + /// </summary> + public uint CreatorHostOs; + /// <summary> + /// Offset 0x28, Original hard disk size, in bytes + /// </summary> + public ulong OriginalSize; + /// <summary> + /// Offset 0x30, Current hard disk size, in bytes + /// </summary> + public ulong CurrentSize; + /// <summary> + /// Offset 0x38, CHS geometry + /// Cylinder mask = 0xFFFF0000 + /// Heads mask = 0x0000FF00 + /// Sectors mask = 0x000000FF + /// </summary> + public uint DiskGeometry; + /// <summary> + /// Offset 0x3C, Disk image type + /// </summary> + public uint DiskType; + /// <summary> + /// Offset 0x40, Checksum for this structure + /// </summary> + public uint Checksum; + /// <summary> + /// Offset 0x44, UUID, used to associate parent with differencing disk images + /// </summary> + public Guid UniqueId; + /// <summary> + /// Offset 0x54, If set, system is saved, so compaction and expansion cannot be performed + /// </summary> + public byte SavedState; + /// <summary> + /// Offset 0x55, 427 bytes reserved, should contain zeros. + /// </summary> + public byte[] Reserved; + } + + struct ParentLocatorEntry + { + /// <summary> + /// Offset 0x00, Describes the platform specific type this entry belongs to + /// </summary> + public uint PlatformCode; + /// <summary> + /// Offset 0x04, Describes the number of 512 bytes sectors used by this entry + /// </summary> + public uint PlatformDataSpace; + /// <summary> + /// Offset 0x08, Describes this entry's size in bytes + /// </summary> + public uint PlatformDataLength; + /// <summary> + /// Offset 0x0c, Reserved + /// </summary> + public uint Reserved; + /// <summary> + /// Offset 0x10, Offset on disk image this entry resides on + /// </summary> + public ulong PlatformDataOffset; + } + + struct DynamicDiskHeader + { + /// <summary> + /// Offset 0x00, Header magic, <see cref="Vhd.DYNAMIC_COOKIE" /> + /// </summary> + public ulong Cookie; + /// <summary> + /// Offset 0x08, Offset to next structure on disk image. + /// Currently unused, 0xFFFFFFFF + /// </summary> + public ulong DataOffset; + /// <summary> + /// Offset 0x10, Offset of the Block Allocation Table (BAT) + /// </summary> + public ulong TableOffset; + /// <summary> + /// Offset 0x18, Version of this header + /// </summary> + public uint HeaderVersion; + /// <summary> + /// Offset 0x1C, Maximum entries present in the BAT + /// </summary> + public uint MaxTableEntries; + /// <summary> + /// Offset 0x20, Size of a block in bytes + /// Should always be a power of two of 512 + /// </summary> + public uint BlockSize; + /// <summary> + /// Offset 0x24, Checksum of this header + /// </summary> + public uint Checksum; + /// <summary> + /// Offset 0x28, UUID of parent disk image for differencing type + /// </summary> + public Guid ParentId; + /// <summary> + /// Offset 0x38, Timestamp of parent disk image + /// </summary> + public uint ParentTimestamp; + /// <summary> + /// Offset 0x3C, Reserved + /// </summary> + public uint Reserved; + /// <summary> + /// Offset 0x40, 512 bytes UTF-16 of parent disk image filename + /// </summary> + public string ParentName; + /// <summary> + /// Offset 0x240, Parent disk image locator entry, <see cref="ParentLocatorEntry" /> + /// </summary> + public ParentLocatorEntry[] LocatorEntries; + /// <summary> + /// Offset 0x300, 256 reserved bytes + /// </summary> + public byte[] Reserved2; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct BatSector + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public uint[] blockPointer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/Unsupported.cs b/DiscImageChef.DiscImages/VHD/Unsupported.cs new file mode 100644 index 000000000..b17a95349 --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhd + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/VHD.cs b/DiscImageChef.DiscImages/VHD/VHD.cs new file mode 100644 index 000000000..6ddacd18c --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/VHD.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : VHD.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + /// <inheritdoc /> + /// <summary> + /// Supports Connectix/Microsoft Virtual PC hard disk image format + /// Until Virtual PC 5 there existed no format, and the hard disk image was + /// merely a sector by sector (RAW) image with a resource fork giving + /// information to Virtual PC itself. + /// </summary> + public partial class Vhd : IWritableImage + { + uint bitmapSize; + uint[] blockAllocationTable; + ImageInfo imageInfo; + byte[][] locatorEntriesData; + DateTime parentDateTime; + IMediaImage parentImage; + DateTime thisDateTime; + DynamicDiskHeader thisDynamic; + IFilter thisFilter; + HardDiskFooter thisFooter; + FileStream writingStream; + + public Vhd() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHD/Write.cs b/DiscImageChef.DiscImages/VHD/Write.cs new file mode 100644 index 000000000..f9fdb6d45 --- /dev/null +++ b/DiscImageChef.DiscImages/VHD/Write.cs @@ -0,0 +1,297 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interop; +using DiscImageChef.CommonTypes.Structs; +using Schemas; +using PlatformID = DiscImageChef.CommonTypes.Interop.PlatformID; +using Version = System.Version; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhd + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(0 + sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + // TODO: Implement dynamic + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(0 + sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + Version thisVersion = GetType().Assembly.GetName().Version; + + if(imageInfo.Cylinders == 0) + { + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + while(imageInfo.Cylinders == 0) + { + imageInfo.Heads--; + + if(imageInfo.Heads == 0) + { + imageInfo.SectorsPerTrack--; + imageInfo.Heads = 16; + } + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); + + if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; + } + } + + HardDiskFooter footer = new HardDiskFooter + { + Cookie = IMAGE_COOKIE, + Features = FEATURES_RESERVED, + Version = VERSION1, + Timestamp = + (uint)(DateTime.Now - new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds, + CreatorApplication = CREATOR_DISCIMAGECHEF, + CreatorVersion = + (uint)(((thisVersion.Major & 0xFF) << 24) + ((thisVersion.Minor & 0xFF) << 16) + + ((thisVersion.Build & 0xFF) << 8) + (thisVersion.Revision & 0xFF)), + CreatorHostOs = DetectOS.GetRealPlatformID() == PlatformID.MacOSX ? CREATOR_MACINTOSH : CREATOR_WINDOWS, + DiskType = TYPE_FIXED, + UniqueId = Guid.NewGuid(), + DiskGeometry = + ((imageInfo.Cylinders & 0xFFFF) << 16) + ((imageInfo.Heads & 0xFF) << 8) + + (imageInfo.SectorsPerTrack & 0xFF), + OriginalSize = imageInfo.Sectors * 512, + CurrentSize = imageInfo.Sectors * 512 + }; + footer.Offset = footer.DiskType == TYPE_FIXED ? ulong.MaxValue : 512; + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + byte[] footerBytes = new byte[512]; + Array.Copy(BigEndianBitConverter.GetBytes(footer.Cookie), 0, footerBytes, 0x00, 8); + Array.Copy(BigEndianBitConverter.GetBytes(footer.Features), 0, footerBytes, 0x08, 4); + Array.Copy(BigEndianBitConverter.GetBytes(footer.Version), 0, footerBytes, 0x0C, 4); + Array.Copy(BigEndianBitConverter.GetBytes(footer.Offset), 0, footerBytes, 0x10, 8); + Array.Copy(BigEndianBitConverter.GetBytes(footer.Timestamp), 0, footerBytes, 0x18, 4); + Array.Copy(BigEndianBitConverter.GetBytes(footer.CreatorApplication), 0, footerBytes, 0x1C, 4); + Array.Copy(BigEndianBitConverter.GetBytes(footer.CreatorVersion), 0, footerBytes, 0x20, 4); + Array.Copy(BigEndianBitConverter.GetBytes(footer.CreatorHostOs), 0, footerBytes, 0x24, 4); + Array.Copy(BigEndianBitConverter.GetBytes(footer.OriginalSize), 0, footerBytes, 0x28, 8); + Array.Copy(BigEndianBitConverter.GetBytes(footer.CurrentSize), 0, footerBytes, 0x30, 8); + Array.Copy(BigEndianBitConverter.GetBytes(footer.DiskGeometry), 0, footerBytes, 0x38, 4); + Array.Copy(BigEndianBitConverter.GetBytes(footer.DiskType), 0, footerBytes, 0x3C, 4); + Array.Copy(footer.UniqueId.ToByteArray(), 0, footerBytes, 0x44, 4); + + footer.Checksum = VhdChecksum(footerBytes); + Array.Copy(BigEndianBitConverter.GetBytes(footer.Checksum), 0, footerBytes, 0x40, 4); + + writingStream.Seek((long)(footer.DiskType == TYPE_FIXED ? footer.OriginalSize : 0), SeekOrigin.Begin); + writingStream.Write(footerBytes, 0, 512); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > 0xFFFF) + { + ErrorMessage = "Too many cylinders."; + return false; + } + + if(heads > 0xFF) + { + ErrorMessage = "Too many heads."; + return false; + } + + if(sectorsPerTrack > 0xFF) + { + ErrorMessage = "Too many sectors per track."; + return false; + } + + imageInfo.SectorsPerTrack = sectorsPerTrack; + imageInfo.Heads = heads; + imageInfo.Cylinders = cylinders; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX/Constants.cs b/DiscImageChef.DiscImages/VHDX/Constants.cs new file mode 100644 index 000000000..e625ecbb9 --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX/Constants.cs @@ -0,0 +1,90 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Microsoft Hyper-V disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ----------------------------------------------------------------------------c +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhdx + { + const ulong VHDX_SIGNATURE = 0x656C696678646876; + const uint VHDX_HEADER_SIG = 0x64616568; + const uint VHDX_REGION_SIG = 0x69676572; + const ulong VHDX_METADATA_SIG = 0x617461646174656D; + + const string PARENT_LINKAGE_KEY = "parent_linkage"; + const string PARENT_LINKAGE2_KEY = "parent_linkage2"; + const string RELATIVE_PATH_KEY = "relative_path"; + const string VOLUME_PATH_KEY = "volume_path"; + const string ABSOLUTE_WIN32_PATH_KEY = "absolute_win32_path"; + + const uint REGION_FLAGS_REQUIRED = 0x01; + + const uint METADATA_FLAGS_USER = 0x01; + const uint METADATA_FLAGS_VIRTUAL = 0x02; + const uint METADATA_FLAGS_REQUIRED = 0x04; + + const uint FILE_FLAGS_LEAVE_ALLOCATED = 0x01; + const uint FILE_FLAGS_HAS_PARENT = 0x02; + + /// <summary>Block has never been stored on this image, check parent</summary> + const ulong PAYLOAD_BLOCK_NOT_PRESENT = 0x00; + /// <summary>Block was stored on this image and is removed, return whatever data you wish</summary> + const ulong PAYLOAD_BLOCK_UNDEFINED = 0x01; + /// <summary>Block is filled with zeroes</summary> + const ulong PAYLOAD_BLOCK_ZERO = 0x02; + /// <summary>All sectors in this block were UNMAPed/TRIMed, return zeroes</summary> + const ulong PAYLOAD_BLOCK_UNMAPPER = 0x03; + /// <summary>Block is present on this image</summary> + const ulong PAYLOAD_BLOCK_FULLY_PRESENT = 0x06; + /// <summary>Block is present on image but there may be sectors present on parent image</summary> + const ulong PAYLOAD_BLOCK_PARTIALLY_PRESENT = 0x07; + + const ulong SECTOR_BITMAP_NOT_PRESENT = 0x00; + const ulong SECTOR_BITMAP_PRESENT = 0x06; + + const ulong BAT_FILE_OFFSET_MASK = 0xFFFFFFFFFFFC0000; + const ulong BAT_FLAGS_MASK = 0x7; + const ulong BAT_RESERVED_MASK = 0x3FFF8; + + const int MAX_CACHE_SIZE = 16777216; + readonly Guid batGuid = new Guid("2DC27766-F623-4200-9D64-115E9BFD4A08"); + readonly Guid fileParametersGuid = new Guid("CAA16737-FA36-4D43-B3B6-33F0AA44E76B"); + readonly Guid logicalSectorSizeGuid = new Guid("8141BF1D-A96F-4709-BA47-F233A8FAAB5F"); + readonly Guid metadataGuid = new Guid("8B7CA206-4790-4B9A-B8FE-575F050F886E"); + readonly Guid page83DataGuid = new Guid("BECA12AB-B2E6-4523-93EF-C309E000C746"); + readonly Guid parentLocatorGuid = new Guid("A8D35F2D-B30B-454D-ABF7-D3D84834AB0C"); + readonly Guid parentTypeVhdxGuid = new Guid("B04AEFB7-D19E-4A81-B789-25B8E9445913"); + readonly Guid physicalSectorSizeGuid = new Guid("CDA348C7-445D-4471-9CC9-E9885251C556"); + readonly Guid virtualDiskSizeGuid = new Guid("2FA54224-CD1B-4876-B211-5DBED83BF4B8"); + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX/Helpers.cs b/DiscImageChef.DiscImages/VHDX/Helpers.cs new file mode 100644 index 000000000..e0df0e12a --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX/Helpers.cs @@ -0,0 +1,58 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for Microsoft Hyper-V disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Linq; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhdx + { + bool CheckBitmap(ulong sectorAddress) + { + long index = (long)(sectorAddress / 8); + int shift = (int)(sectorAddress % 8); + byte val = (byte)(1 << shift); + + if(index > sectorBitmap.LongLength) return false; + + return (sectorBitmap[index] & val) == val; + } + + static uint VhdxChecksum(IEnumerable<byte> data) + { + uint checksum = data.Aggregate<byte, uint>(0, (current, b) => current + b); + + return ~checksum; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX/Identify.cs b/DiscImageChef.DiscImages/VHDX/Identify.cs new file mode 100644 index 000000000..72f704e3f --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX/Identify.cs @@ -0,0 +1,60 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Microsoft Hyper-V disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhdx + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) return false; + + byte[] vhdxIdB = new byte[Marshal.SizeOf(vhdxId)]; + stream.Read(vhdxIdB, 0, Marshal.SizeOf(vhdxId)); + vhdxId = new VhdxIdentifier(); + IntPtr idPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vhdxId)); + Marshal.Copy(vhdxIdB, 0, idPtr, Marshal.SizeOf(vhdxId)); + vhdxId = (VhdxIdentifier)Marshal.PtrToStructure(idPtr, typeof(VhdxIdentifier)); + Marshal.FreeHGlobal(idPtr); + + return vhdxId.signature == VHDX_SIGNATURE; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX/Properties.cs b/DiscImageChef.DiscImages/VHDX/Properties.cs new file mode 100644 index 000000000..ed22e56e0 --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX/Properties.cs @@ -0,0 +1,63 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Microsoft Hyper-V disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhdx + { + public ImageInfo Info => imageInfo; + + public string Name => "Microsoft VHDX"; + public Guid Id => new Guid("536B141B-D09C-4799-AB70-34631286EB9D"); + + public string Format => "VHDX"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX.cs b/DiscImageChef.DiscImages/VHDX/Read.cs similarity index 57% rename from DiscImageChef.DiscImages/VHDX.cs rename to DiscImageChef.DiscImages/VHDX/Read.cs index b5cd5d22d..f2a4ddf2b 100644 --- a/DiscImageChef.DiscImages/VHDX.cs +++ b/DiscImageChef.DiscImages/VHDX/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : VHDX.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages Microsoft Hyper-V disk images. +// Reads Microsoft Hyper-V disk images. // // --[ License ] -------------------------------------------------------------- // @@ -33,168 +33,18 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Text; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; namespace DiscImageChef.DiscImages { - public class Vhdx : IMediaImage + public partial class Vhdx { - const ulong VHDX_SIGNATURE = 0x656C696678646876; - const uint VHDX_HEADER_SIG = 0x64616568; - const uint VHDX_REGION_SIG = 0x69676572; - const ulong VHDX_METADATA_SIG = 0x617461646174656D; - - const string PARENT_LINKAGE_KEY = "parent_linkage"; - const string PARENT_LINKAGE2_KEY = "parent_linkage2"; - const string RELATIVE_PATH_KEY = "relative_path"; - const string VOLUME_PATH_KEY = "volume_path"; - const string ABSOLUTE_WIN32_PATH_KEY = "absolute_win32_path"; - - const uint REGION_FLAGS_REQUIRED = 0x01; - - const uint METADATA_FLAGS_USER = 0x01; - const uint METADATA_FLAGS_VIRTUAL = 0x02; - const uint METADATA_FLAGS_REQUIRED = 0x04; - - const uint FILE_FLAGS_LEAVE_ALLOCATED = 0x01; - const uint FILE_FLAGS_HAS_PARENT = 0x02; - - /// <summary>Block has never been stored on this image, check parent</summary> - const ulong PAYLOAD_BLOCK_NOT_PRESENT = 0x00; - /// <summary>Block was stored on this image and is removed, return whatever data you wish</summary> - const ulong PAYLOAD_BLOCK_UNDEFINED = 0x01; - /// <summary>Block is filled with zeroes</summary> - const ulong PAYLOAD_BLOCK_ZERO = 0x02; - /// <summary>All sectors in this block were UNMAPed/TRIMed, return zeroes</summary> - const ulong PAYLOAD_BLOCK_UNMAPPER = 0x03; - /// <summary>Block is present on this image</summary> - const ulong PAYLOAD_BLOCK_FULLY_PRESENT = 0x06; - /// <summary>Block is present on image but there may be sectors present on parent image</summary> - const ulong PAYLOAD_BLOCK_PARTIALLY_PRESENT = 0x07; - - const ulong SECTOR_BITMAP_NOT_PRESENT = 0x00; - const ulong SECTOR_BITMAP_PRESENT = 0x06; - - const ulong BAT_FILE_OFFSET_MASK = 0xFFFFFFFFFFFC0000; - const ulong BAT_FLAGS_MASK = 0x7; - const ulong BAT_RESERVED_MASK = 0x3FFF8; - - const int MAX_CACHE_SIZE = 16777216; - readonly Guid batGuid = new Guid("2DC27766-F623-4200-9D64-115E9BFD4A08"); - readonly Guid fileParametersGuid = new Guid("CAA16737-FA36-4D43-B3B6-33F0AA44E76B"); - readonly Guid logicalSectorSizeGuid = new Guid("8141BF1D-A96F-4709-BA47-F233A8FAAB5F"); - readonly Guid metadataGuid = new Guid("8B7CA206-4790-4B9A-B8FE-575F050F886E"); - readonly Guid page83DataGuid = new Guid("BECA12AB-B2E6-4523-93EF-C309E000C746"); - readonly Guid parentLocatorGuid = new Guid("A8D35F2D-B30B-454D-ABF7-D3D84834AB0C"); - readonly Guid parentTypeVhdxGuid = new Guid("B04AEFB7-D19E-4A81-B789-25B8E9445913"); - readonly Guid physicalSectorSizeGuid = new Guid("CDA348C7-445D-4471-9CC9-E9885251C556"); - readonly Guid virtualDiskSizeGuid = new Guid("2FA54224-CD1B-4876-B211-5DBED83BF4B8"); - - long batOffset; - - ulong[] blockAllocationTable; - Dictionary<ulong, byte[]> blockCache; - - long chunkRatio; - ulong dataBlocks; - bool hasParent; - ImageInfo imageInfo; - Stream imageStream; - uint logicalSectorSize; - int maxBlockCache; - int maxSectorCache; - long metadataOffset; - Guid page83Data; - IMediaImage parentImage; - uint physicalSectorSize; - byte[] sectorBitmap; - ulong[] sectorBitmapPointers; - - Dictionary<ulong, byte[]> sectorCache; - VhdxFileParameters vFileParms; - VhdxHeader vHdr; - - VhdxIdentifier vhdxId; - - ulong virtualDiskSize; - VhdxMetadataTableHeader vMetHdr; - VhdxMetadataTableEntry[] vMets; - VhdxParentLocatorHeader vParHdr; - VhdxParentLocatorEntry[] vPars; - VhdxRegionTableHeader vRegHdr; - VhdxRegionTableEntry[] vRegs; - - public Vhdx() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Microsoft VHDX"; - public Guid Id => new Guid("536B141B-D09C-4799-AB70-34631286EB9D"); - - public string Format => "VHDX"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - - if(stream.Length < 512) return false; - - byte[] vhdxIdB = new byte[Marshal.SizeOf(vhdxId)]; - stream.Read(vhdxIdB, 0, Marshal.SizeOf(vhdxId)); - vhdxId = new VhdxIdentifier(); - IntPtr idPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vhdxId)); - Marshal.Copy(vhdxIdB, 0, idPtr, Marshal.SizeOf(vhdxId)); - vhdxId = (VhdxIdentifier)Marshal.PtrToStructure(idPtr, typeof(VhdxIdentifier)); - Marshal.FreeHGlobal(idPtr); - - return vhdxId.signature == VHDX_SIGNATURE; - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -651,323 +501,5 @@ namespace DiscImageChef.DiscImages return ms.ToArray(); } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - bool CheckBitmap(ulong sectorAddress) - { - long index = (long)(sectorAddress / 8); - int shift = (int)(sectorAddress % 8); - byte val = (byte)(1 << shift); - - if(index > sectorBitmap.LongLength) return false; - - return (sectorBitmap[index] & val) == val; - } - - static uint VhdxChecksum(IEnumerable<byte> data) - { - uint checksum = data.Aggregate<byte, uint>(0, (current, b) => current + b); - - return ~checksum; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxIdentifier - { - /// <summary> - /// Signature, <see cref="Vhdx.VHDX_SIGNATURE" /> - /// </summary> - public ulong signature; - /// <summary> - /// UTF-16 string containing creator - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] - public byte[] creator; - } - - #pragma warning disable 649 - #pragma warning disable 169 - struct VhdxHeader - { - /// <summary> - /// Signature, <see cref="Vhdx.VHDX_HEADER_SIG" /> - /// </summary> - public uint Signature; - /// <summary> - /// CRC-32C of whole 4096 bytes header with this field set to 0 - /// </summary> - public uint Checksum; - /// <summary> - /// Sequence number - /// </summary> - public ulong Sequence; - /// <summary> - /// Unique identifier for file contents, must be changed on first write to metadata - /// </summary> - public Guid FileWriteGuid; - /// <summary> - /// Unique identifier for disk contents, must be changed on first write to metadata or data - /// </summary> - public Guid DataWriteGuid; - /// <summary> - /// Unique identifier for log entries - /// </summary> - public Guid LogGuid; - /// <summary> - /// Version of log format - /// </summary> - public ushort LogVersion; - /// <summary> - /// Version of VHDX format - /// </summary> - public ushort Version; - /// <summary> - /// Length in bytes of the log - /// </summary> - public uint LogLength; - /// <summary> - /// Offset from image start to the log - /// </summary> - public ulong LogOffset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4016)] - public byte[] Reserved; - } - #pragma warning restore 649 - #pragma warning restore 169 - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxRegionTableHeader - { - /// <summary> - /// Signature, <see cref="Vhdx.VHDX_REGION_SIG" /> - /// </summary> - public uint signature; - /// <summary> - /// CRC-32C of whole 64Kb table with this field set to 0 - /// </summary> - public uint checksum; - /// <summary> - /// How many entries follow this table - /// </summary> - public uint entries; - /// <summary> - /// Reserved - /// </summary> - public uint reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxRegionTableEntry - { - /// <summary> - /// Object identifier - /// </summary> - public Guid guid; - /// <summary> - /// Offset in image of the object - /// </summary> - public ulong offset; - /// <summary> - /// Length in bytes of the object - /// </summary> - public uint length; - /// <summary> - /// Flags - /// </summary> - public uint flags; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxMetadataTableHeader - { - /// <summary> - /// Signature - /// </summary> - public ulong signature; - /// <summary> - /// Reserved - /// </summary> - public ushort reserved; - /// <summary> - /// How many entries are in the table - /// </summary> - public ushort entries; - /// <summary> - /// Reserved - /// </summary> - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] - public uint[] reserved2; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxMetadataTableEntry - { - /// <summary> - /// Metadata ID - /// </summary> - public Guid itemId; - /// <summary> - /// Offset relative to start of metadata region - /// </summary> - public uint offset; - /// <summary> - /// Length in bytes - /// </summary> - public uint length; - /// <summary> - /// Flags - /// </summary> - public uint flags; - /// <summary> - /// Reserved - /// </summary> - public uint reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxFileParameters - { - /// <summary> - /// Block size in bytes - /// </summary> - public uint blockSize; - /// <summary> - /// Flags - /// </summary> - public uint flags; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxParentLocatorHeader - { - /// <summary> - /// Type of parent virtual disk - /// </summary> - public Guid locatorType; - public ushort reserved; - /// <summary> - /// How many KVPs are in this parent locator - /// </summary> - public ushort keyValueCount; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VhdxParentLocatorEntry - { - /// <summary> - /// Offset from metadata to key - /// </summary> - public uint keyOffset; - /// <summary> - /// Offset from metadata to value - /// </summary> - public uint valueOffset; - /// <summary> - /// Size of key - /// </summary> - public ushort keyLength; - /// <summary> - /// Size of value - /// </summary> - public ushort valueLength; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX/Structs.cs b/DiscImageChef.DiscImages/VHDX/Structs.cs new file mode 100644 index 000000000..e81a1b3a8 --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX/Structs.cs @@ -0,0 +1,241 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Microsoft Hyper-V disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhdx + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxIdentifier + { + /// <summary> + /// Signature, <see cref="Vhdx.VHDX_SIGNATURE" /> + /// </summary> + public ulong signature; + /// <summary> + /// UTF-16 string containing creator + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] + public byte[] creator; + } + + #pragma warning disable 649 + #pragma warning disable 169 + struct VhdxHeader + { + /// <summary> + /// Signature, <see cref="Vhdx.VHDX_HEADER_SIG" /> + /// </summary> + public uint Signature; + /// <summary> + /// CRC-32C of whole 4096 bytes header with this field set to 0 + /// </summary> + public uint Checksum; + /// <summary> + /// Sequence number + /// </summary> + public ulong Sequence; + /// <summary> + /// Unique identifier for file contents, must be changed on first write to metadata + /// </summary> + public Guid FileWriteGuid; + /// <summary> + /// Unique identifier for disk contents, must be changed on first write to metadata or data + /// </summary> + public Guid DataWriteGuid; + /// <summary> + /// Unique identifier for log entries + /// </summary> + public Guid LogGuid; + /// <summary> + /// Version of log format + /// </summary> + public ushort LogVersion; + /// <summary> + /// Version of VHDX format + /// </summary> + public ushort Version; + /// <summary> + /// Length in bytes of the log + /// </summary> + public uint LogLength; + /// <summary> + /// Offset from image start to the log + /// </summary> + public ulong LogOffset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4016)] + public byte[] Reserved; + } + #pragma warning restore 649 + #pragma warning restore 169 + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxRegionTableHeader + { + /// <summary> + /// Signature, <see cref="Vhdx.VHDX_REGION_SIG" /> + /// </summary> + public uint signature; + /// <summary> + /// CRC-32C of whole 64Kb table with this field set to 0 + /// </summary> + public uint checksum; + /// <summary> + /// How many entries follow this table + /// </summary> + public uint entries; + /// <summary> + /// Reserved + /// </summary> + public uint reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxRegionTableEntry + { + /// <summary> + /// Object identifier + /// </summary> + public Guid guid; + /// <summary> + /// Offset in image of the object + /// </summary> + public ulong offset; + /// <summary> + /// Length in bytes of the object + /// </summary> + public uint length; + /// <summary> + /// Flags + /// </summary> + public uint flags; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxMetadataTableHeader + { + /// <summary> + /// Signature + /// </summary> + public ulong signature; + /// <summary> + /// Reserved + /// </summary> + public ushort reserved; + /// <summary> + /// How many entries are in the table + /// </summary> + public ushort entries; + /// <summary> + /// Reserved + /// </summary> + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public uint[] reserved2; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxMetadataTableEntry + { + /// <summary> + /// Metadata ID + /// </summary> + public Guid itemId; + /// <summary> + /// Offset relative to start of metadata region + /// </summary> + public uint offset; + /// <summary> + /// Length in bytes + /// </summary> + public uint length; + /// <summary> + /// Flags + /// </summary> + public uint flags; + /// <summary> + /// Reserved + /// </summary> + public uint reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxFileParameters + { + /// <summary> + /// Block size in bytes + /// </summary> + public uint blockSize; + /// <summary> + /// Flags + /// </summary> + public uint flags; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxParentLocatorHeader + { + /// <summary> + /// Type of parent virtual disk + /// </summary> + public Guid locatorType; + public ushort reserved; + /// <summary> + /// How many KVPs are in this parent locator + /// </summary> + public ushort keyValueCount; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VhdxParentLocatorEntry + { + /// <summary> + /// Offset from metadata to key + /// </summary> + public uint keyOffset; + /// <summary> + /// Offset from metadata to value + /// </summary> + public uint valueOffset; + /// <summary> + /// Size of key + /// </summary> + public ushort keyLength; + /// <summary> + /// Size of value + /// </summary> + public ushort valueLength; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX/Unsupported.cs b/DiscImageChef.DiscImages/VHDX/Unsupported.cs new file mode 100644 index 000000000..6a677a0a7 --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Microsoft Hyper-V disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhdx + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VHDX/VHDX.cs b/DiscImageChef.DiscImages/VHDX/VHDX.cs new file mode 100644 index 000000000..ed0ef7eeb --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX/VHDX.cs @@ -0,0 +1,100 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : VHDX.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Microsoft Hyper-V disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Vhdx : IMediaImage + { + long batOffset; + ulong[] blockAllocationTable; + Dictionary<ulong, byte[]> blockCache; + long chunkRatio; + ulong dataBlocks; + bool hasParent; + ImageInfo imageInfo; + Stream imageStream; + uint logicalSectorSize; + int maxBlockCache; + int maxSectorCache; + long metadataOffset; + Guid page83Data; + IMediaImage parentImage; + uint physicalSectorSize; + byte[] sectorBitmap; + ulong[] sectorBitmapPointers; + Dictionary<ulong, byte[]> sectorCache; + VhdxFileParameters vFileParms; + VhdxHeader vHdr; + VhdxIdentifier vhdxId; + ulong virtualDiskSize; + VhdxMetadataTableHeader vMetHdr; + VhdxMetadataTableEntry[] vMets; + VhdxParentLocatorHeader vParHdr; + VhdxParentLocatorEntry[] vPars; + VhdxRegionTableHeader vRegHdr; + VhdxRegionTableEntry[] vRegs; + + public Vhdx() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware/Constants.cs b/DiscImageChef.DiscImages/VMware/Constants.cs new file mode 100644 index 000000000..8a2fc6239 --- /dev/null +++ b/DiscImageChef.DiscImages/VMware/Constants.cs @@ -0,0 +1,93 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for VMware disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class VMware + { + const uint VMWARE_EXTENT_MAGIC = 0x564D444B; + const uint VMWARE_COW_MAGIC = 0x44574F43; + + const string VM_TYPE_CUSTOM = "custom"; + const string VM_TYPE_MONO_SPARSE = "monolithicSparse"; + const string VM_TYPE_MONO_FLAT = "monolithicFlat"; + const string VM_TYPE_SPLIT_SPARSE = "twoGbMaxExtentSparse"; + const string VM_TYPE_SPLIT_FLAT = "twoGbMaxExtentFlat"; + const string VM_TYPE_FULL_DEVICE = "fullDevice"; + const string VM_TYPE_PART_DEVICE = "partitionedDevice"; + const string VMFS_TYPE_FLAT = "vmfsPreallocated"; + const string VMFS_TYPE_ZERO = "vmfsEagerZeroedThick"; + const string VMFS_TYPE_THIN = "vmfsThin"; + const string VMFS_TYPE_SPARSE = "vmfsSparse"; + const string VMFS_TYPE_RDM = "vmfsRDM"; + const string VMFS_TYPE_RDM_OLD = "vmfsRawDeviceMap"; + const string VMFS_TYPE_RDMP = "vmfsRDMP"; + const string VMFS_TYPE_RDMP_OLD = "vmfsPassthroughRawDeviceMap"; + const string VMFS_TYPE_RAW = "vmfsRaw"; + const string VMFS_TYPE = "vmfs"; + const string VM_TYPE_STREAM = "streamOptimized"; + + const string DDF_MAGIC = "# Disk DescriptorFile"; + + const string REGEX_VERSION = @"^\s*version\s*=\s*(?<version>\d+)$"; + const string REGEX_CID = @"^\s*CID\s*=\s*(?<cid>[0123456789abcdef]{8})$"; + const string REGEX_CID_PARENT = @"^\s*parentCID\s*=\s*(?<cid>[0123456789abcdef]{8})$"; + const string REGEX_TYPE = + @"^\s*createType\s*=\s*\""(?<type>custom|monolithicSparse|monolithicFlat|twoGbMaxExtentSparse|twoGbMaxExtentFlat|fullDevice|partitionedDevice|vmfs|vmfsPreallocated|vmfsEagerZeroedThick|vmfsThin|vmfsSparse|vmfsRDM|vmfsRawDeviceMap|vmfsRDMP|vmfsPassthroughRawDeviceMap|vmfsRaw|streamOptimized)\""$"; + const string REGEX_EXTENT = + @"^\s*(?<access>(RW|RDONLY|NOACCESS))\s+(?<sectors>\d+)\s+(?<type>(FLAT|SPARSE|ZERO|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW))\s+\""(?<filename>.+)\""(\s*(?<offset>\d+))?$"; + const string REGEX_DDB_TYPE = + @"^\s*ddb\.adapterType\s*=\s*\""(?<type>ide|buslogic|lsilogic|legacyESX)\""$"; + const string REGEX_DDB_SECTORS = @"^\s*ddb\.geometry\.sectors\s*=\s*\""(?<sectors>\d+)\""$"; + const string REGEX_DDB_HEADS = @"^\s*ddb\.geometry\.heads\s*=\s*\""(?<heads>\d+)\""$"; + const string REGEX_DDB_CYLINDERS = @"^\s*ddb\.geometry\.cylinders\s*=\s*\""(?<cylinders>\d+)\""$"; + const string PARENT_REGEX = @"^\s*parentFileNameHint\s*=\s*\""(?<filename>.+)\""$"; + + const uint FLAGS_VALID_NEW_LINE = 0x01; + const uint FLAGS_USE_REDUNDANT_TABLE = 0x02; + const uint FLAGS_ZERO_GRAIN_GTE = 0x04; + const uint FLAGS_COMPRESSION = 0x10000; + const uint FLAGS_MARKERS = 0x20000; + + const ushort COMPRESSION_NONE = 0; + const ushort COMPRESSION_DEFLATE = 1; + + const uint MAX_CACHE_SIZE = 16777216; + const uint SECTOR_SIZE = 512; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; + readonly byte[] ddfMagicBytes = + { + 0x23, 0x20, 0x44, 0x69, 0x73, 0x6B, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6F, 0x72, 0x46, + 0x69, 0x6C, 0x65 + }; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware/Identify.cs b/DiscImageChef.DiscImages/VMware/Identify.cs new file mode 100644 index 000000000..b720824d0 --- /dev/null +++ b/DiscImageChef.DiscImages/VMware/Identify.cs @@ -0,0 +1,86 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies VMware disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class VMware + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + byte[] ddfMagic = new byte[0x15]; + + if(stream.Length > Marshal.SizeOf(vmEHdr)) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] vmEHdrB = new byte[Marshal.SizeOf(vmEHdr)]; + stream.Read(vmEHdrB, 0, Marshal.SizeOf(vmEHdr)); + vmEHdr = new VMwareExtentHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vmEHdr)); + Marshal.Copy(vmEHdrB, 0, headerPtr, Marshal.SizeOf(vmEHdr)); + vmEHdr = (VMwareExtentHeader)Marshal.PtrToStructure(headerPtr, typeof(VMwareExtentHeader)); + Marshal.FreeHGlobal(headerPtr); + + stream.Seek(0, SeekOrigin.Begin); + stream.Read(ddfMagic, 0, 0x15); + + vmCHdr = new VMwareCowHeader(); + if(stream.Length <= Marshal.SizeOf(vmCHdr)) + return ddfMagicBytes.SequenceEqual(ddfMagic) || vmEHdr.magic == VMWARE_EXTENT_MAGIC || + vmCHdr.magic == VMWARE_COW_MAGIC; + + stream.Seek(0, SeekOrigin.Begin); + byte[] vmCHdrB = new byte[Marshal.SizeOf(vmCHdr)]; + stream.Read(vmCHdrB, 0, Marshal.SizeOf(vmCHdr)); + headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vmCHdr)); + Marshal.Copy(vmCHdrB, 0, headerPtr, Marshal.SizeOf(vmCHdr)); + vmCHdr = (VMwareCowHeader)Marshal.PtrToStructure(headerPtr, typeof(VMwareCowHeader)); + Marshal.FreeHGlobal(headerPtr); + + return ddfMagicBytes.SequenceEqual(ddfMagic) || vmEHdr.magic == VMWARE_EXTENT_MAGIC || + vmCHdr.magic == VMWARE_COW_MAGIC; + } + + stream.Seek(0, SeekOrigin.Begin); + stream.Read(ddfMagic, 0, 0x15); + + return ddfMagicBytes.SequenceEqual(ddfMagic); + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware/Properties.cs b/DiscImageChef.DiscImages/VMware/Properties.cs new file mode 100644 index 000000000..02e03dddd --- /dev/null +++ b/DiscImageChef.DiscImages/VMware/Properties.cs @@ -0,0 +1,84 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for VMware disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class VMware + { + public ImageInfo Info => imageInfo; + + public string Name => "VMware disk image"; + public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); + + public string Format => "VMware"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new[] + { + ("adapter_type", typeof(string), + "Type of adapter type. Possible values: ide, lsilogic, buslogic, legacyESX."), + ("hwversion", typeof(uint), "VDMK hardware version."), ("sparse", typeof(bool), "Use sparse extents."), + ("split", typeof(bool), "Split data file at 2GiB.") + }; + public IEnumerable<string> KnownExtensions => new[] {".vmdk"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware.cs b/DiscImageChef.DiscImages/VMware/Read.cs similarity index 52% rename from DiscImageChef.DiscImages/VMware.cs rename to DiscImageChef.DiscImages/VMware/Read.cs index fed2ee5be..f1c94f69d 100644 --- a/DiscImageChef.DiscImages/VMware.cs +++ b/DiscImageChef.DiscImages/VMware/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : VMware.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages VMware disk images. +// Reads VMware disk images. // // --[ License ] -------------------------------------------------------------- // @@ -35,190 +35,17 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text; using System.Text.RegularExpressions; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Filters; -using Schemas; namespace DiscImageChef.DiscImages { - public class VMware : IWritableImage + public partial class VMware { - const uint VMWARE_EXTENT_MAGIC = 0x564D444B; - const uint VMWARE_COW_MAGIC = 0x44574F43; - - const string VM_TYPE_CUSTOM = "custom"; - const string VM_TYPE_MONO_SPARSE = "monolithicSparse"; - const string VM_TYPE_MONO_FLAT = "monolithicFlat"; - const string VM_TYPE_SPLIT_SPARSE = "twoGbMaxExtentSparse"; - const string VM_TYPE_SPLIT_FLAT = "twoGbMaxExtentFlat"; - const string VM_TYPE_FULL_DEVICE = "fullDevice"; - const string VM_TYPE_PART_DEVICE = "partitionedDevice"; - const string VMFS_TYPE_FLAT = "vmfsPreallocated"; - const string VMFS_TYPE_ZERO = "vmfsEagerZeroedThick"; - const string VMFS_TYPE_THIN = "vmfsThin"; - const string VMFS_TYPE_SPARSE = "vmfsSparse"; - const string VMFS_TYPE_RDM = "vmfsRDM"; - const string VMFS_TYPE_RDM_OLD = "vmfsRawDeviceMap"; - const string VMFS_TYPE_RDMP = "vmfsRDMP"; - const string VMFS_TYPE_RDMP_OLD = "vmfsPassthroughRawDeviceMap"; - const string VMFS_TYPE_RAW = "vmfsRaw"; - const string VMFS_TYPE = "vmfs"; - const string VM_TYPE_STREAM = "streamOptimized"; - - const string DDF_MAGIC = "# Disk DescriptorFile"; - - const string REGEX_VERSION = @"^\s*version\s*=\s*(?<version>\d+)$"; - const string REGEX_CID = @"^\s*CID\s*=\s*(?<cid>[0123456789abcdef]{8})$"; - const string REGEX_CID_PARENT = @"^\s*parentCID\s*=\s*(?<cid>[0123456789abcdef]{8})$"; - const string REGEX_TYPE = - @"^\s*createType\s*=\s*\""(?<type>custom|monolithicSparse|monolithicFlat|twoGbMaxExtentSparse|twoGbMaxExtentFlat|fullDevice|partitionedDevice|vmfs|vmfsPreallocated|vmfsEagerZeroedThick|vmfsThin|vmfsSparse|vmfsRDM|vmfsRawDeviceMap|vmfsRDMP|vmfsPassthroughRawDeviceMap|vmfsRaw|streamOptimized)\""$"; - const string REGEX_EXTENT = - @"^\s*(?<access>(RW|RDONLY|NOACCESS))\s+(?<sectors>\d+)\s+(?<type>(FLAT|SPARSE|ZERO|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW))\s+\""(?<filename>.+)\""(\s*(?<offset>\d+))?$"; - const string REGEX_DDB_TYPE = - @"^\s*ddb\.adapterType\s*=\s*\""(?<type>ide|buslogic|lsilogic|legacyESX)\""$"; - const string REGEX_DDB_SECTORS = @"^\s*ddb\.geometry\.sectors\s*=\s*\""(?<sectors>\d+)\""$"; - const string REGEX_DDB_HEADS = @"^\s*ddb\.geometry\.heads\s*=\s*\""(?<heads>\d+)\""$"; - const string REGEX_DDB_CYLINDERS = @"^\s*ddb\.geometry\.cylinders\s*=\s*\""(?<cylinders>\d+)\""$"; - const string PARENT_REGEX = @"^\s*parentFileNameHint\s*=\s*\""(?<filename>.+)\""$"; - - const uint FLAGS_VALID_NEW_LINE = 0x01; - const uint FLAGS_USE_REDUNDANT_TABLE = 0x02; - const uint FLAGS_ZERO_GRAIN_GTE = 0x04; - const uint FLAGS_COMPRESSION = 0x10000; - const uint FLAGS_MARKERS = 0x20000; - - const ushort COMPRESSION_NONE = 0; - const ushort COMPRESSION_DEFLATE = 1; - - const uint MAX_CACHE_SIZE = 16777216; - const uint SECTOR_SIZE = 512; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; - readonly byte[] ddfMagicBytes = - { - 0x23, 0x20, 0x44, 0x69, 0x73, 0x6B, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6F, 0x72, 0x46, - 0x69, 0x6C, 0x65 - }; - string adapter_type; - - uint cid; - StreamWriter descriptorStream; - Dictionary<ulong, VMwareExtent> extents; - IFilter gdFilter; - Dictionary<ulong, byte[]> grainCache; - - ulong grainSize; - uint[] gTable; - bool hasParent; - uint hwversion; - ImageInfo imageInfo; - string imageType; - uint maxCachedGrains; - uint parentCid; - - IMediaImage parentImage; - string parentName; - - Dictionary<ulong, byte[]> sectorCache; - uint version; - VMwareCowHeader vmCHdr; - - VMwareExtentHeader vmEHdr; - string writingBaseName; - FileStream writingStream; - - public VMware() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = "VMware", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "VMware disk image"; - public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); - - public string Format => "VMware"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - - byte[] ddfMagic = new byte[0x15]; - - if(stream.Length > Marshal.SizeOf(vmEHdr)) - { - stream.Seek(0, SeekOrigin.Begin); - byte[] vmEHdrB = new byte[Marshal.SizeOf(vmEHdr)]; - stream.Read(vmEHdrB, 0, Marshal.SizeOf(vmEHdr)); - vmEHdr = new VMwareExtentHeader(); - IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vmEHdr)); - Marshal.Copy(vmEHdrB, 0, headerPtr, Marshal.SizeOf(vmEHdr)); - vmEHdr = (VMwareExtentHeader)Marshal.PtrToStructure(headerPtr, typeof(VMwareExtentHeader)); - Marshal.FreeHGlobal(headerPtr); - - stream.Seek(0, SeekOrigin.Begin); - stream.Read(ddfMagic, 0, 0x15); - - vmCHdr = new VMwareCowHeader(); - if(stream.Length <= Marshal.SizeOf(vmCHdr)) - return ddfMagicBytes.SequenceEqual(ddfMagic) || vmEHdr.magic == VMWARE_EXTENT_MAGIC || - vmCHdr.magic == VMWARE_COW_MAGIC; - - stream.Seek(0, SeekOrigin.Begin); - byte[] vmCHdrB = new byte[Marshal.SizeOf(vmCHdr)]; - stream.Read(vmCHdrB, 0, Marshal.SizeOf(vmCHdr)); - headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vmCHdr)); - Marshal.Copy(vmCHdrB, 0, headerPtr, Marshal.SizeOf(vmCHdr)); - vmCHdr = (VMwareCowHeader)Marshal.PtrToStructure(headerPtr, typeof(VMwareCowHeader)); - Marshal.FreeHGlobal(headerPtr); - - return ddfMagicBytes.SequenceEqual(ddfMagic) || vmEHdr.magic == VMWARE_EXTENT_MAGIC || - vmCHdr.magic == VMWARE_COW_MAGIC; - } - - stream.Seek(0, SeekOrigin.Begin); - stream.Read(ddfMagic, 0, 0x15); - - return ddfMagicBytes.SequenceEqual(ddfMagic); - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -766,486 +593,5 @@ namespace DiscImageChef.DiscImages return ms.ToArray(); } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new[] - { - ("adapter_type", typeof(string), - "Type of adapter type. Possible values: ide, lsilogic, buslogic, legacyESX."), - ("hwversion", typeof(uint), "VDMK hardware version."), ("sparse", typeof(bool), "Use sparse extents."), - ("split", typeof(bool), "Split data file at 2GiB.") - }; - public IEnumerable<string> KnownExtensions => new[] {".vmdk"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(options != null) - { - if(options.TryGetValue("adapter", out adapter_type)) - switch(adapter_type.ToLowerInvariant()) - { - case "ide": - case "lsilogic": - case "buslogic": - adapter_type = adapter_type.ToLowerInvariant(); - break; - case "legacyesx": - adapter_type = "legacyESX"; - break; - default: - ErrorMessage = $"Invalid adapter type {adapter_type}"; - return false; - } - else adapter_type = "ide"; - - if(options.TryGetValue("hwversion", out string tmpValue)) - { - if(!uint.TryParse(tmpValue, out hwversion)) - { - ErrorMessage = "Invalid value for hwversion option"; - return false; - } - } - else hwversion = 4; - - if(options.TryGetValue("split", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out bool tmpBool)) - { - ErrorMessage = "Invalid value for split option"; - return false; - } - - if(tmpBool) - { - ErrorMessage = "Splitted images not yet implemented"; - return false; - } - } - - if(options.TryGetValue("sparse", out tmpValue)) - { - if(!bool.TryParse(tmpValue, out bool tmpBool)) - { - ErrorMessage = "Invalid value for sparse option"; - return false; - } - - if(tmpBool) - { - ErrorMessage = "Sparse images not yet implemented"; - return false; - } - } - } - else - { - adapter_type = "ide"; - hwversion = 4; - } - - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try - { - writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); - descriptorStream = new StreamWriter(path, false, Encoding.ASCII); - // TODO: Support split - writingStream = new FileStream(writingBaseName + "-flat.vmdk", FileMode.OpenOrCreate, - FileAccess.ReadWrite, FileShare.None); - } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - // TODO: Implement sparse and split - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - // TODO: Implement sparse and split - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - writingStream.Flush(); - writingStream.Close(); - - if(imageInfo.Cylinders == 0) - { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - while(imageInfo.Cylinders == 0) - { - imageInfo.Heads--; - - if(imageInfo.Heads == 0) - { - imageInfo.SectorsPerTrack--; - imageInfo.Heads = 16; - } - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); - - if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; - } - } - - descriptorStream.WriteLine("# Disk DescriptorFile"); - descriptorStream.WriteLine("version=1"); - descriptorStream.WriteLine($"CID={new Random().Next(int.MinValue, int.MaxValue):x8}"); - descriptorStream.WriteLine("parentCID=ffffffff"); - descriptorStream.WriteLine("createType=\"monolithicFlat\""); - descriptorStream.WriteLine(); - descriptorStream.WriteLine("# Extent description"); - descriptorStream.WriteLine($"RW {imageInfo.Sectors} FLAT \"{writingBaseName + "-flat.vmdk"}\" 0"); - descriptorStream.WriteLine(); - descriptorStream.WriteLine("# The Disk Data Base"); - descriptorStream.WriteLine("#DDB"); - descriptorStream.WriteLine(); - descriptorStream.WriteLine($"ddb.virtualHWVersion = \"{hwversion}\""); - descriptorStream.WriteLine($"ddb.geometry.cylinders = \"{imageInfo.Cylinders}\""); - descriptorStream.WriteLine($"ddb.geometry.heads = \"{imageInfo.Heads}\""); - descriptorStream.WriteLine($"ddb.geometry.sectors = \"{imageInfo.SectorsPerTrack}\""); - descriptorStream.WriteLine($"ddb.adapterType = \"{adapter_type}\""); - - descriptorStream.Flush(); - descriptorStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > ushort.MaxValue) - { - ErrorMessage = "Too many cylinders."; - return false; - } - - if(heads > byte.MaxValue) - { - ErrorMessage = "Too many heads."; - return false; - } - - if(sectorsPerTrack > byte.MaxValue) - { - ErrorMessage = "Too many sectors per track."; - return false; - } - - imageInfo.SectorsPerTrack = sectorsPerTrack; - imageInfo.Heads = heads; - imageInfo.Cylinders = cylinders; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VMwareExtentHeader - { - public uint magic; - public uint version; - public uint flags; - public ulong capacity; - public ulong grainSize; - public ulong descriptorOffset; - public ulong descriptorSize; - public uint GTEsPerGT; - public ulong rgdOffset; - public ulong gdOffset; - public ulong overhead; - [MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown; - public byte singleEndLineChar; - public byte nonEndLineChar; - public byte doubleEndLineChar1; - public byte doubleEndLineChar2; - public ushort compression; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 433)] - public byte[] padding; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct VMwareCowHeader - { - public uint magic; - public uint version; - public uint flags; - public uint sectors; - public uint grainSize; - public uint gdOffset; - public uint numGDEntries; - public uint freeSector; - public uint cylinders; - public uint heads; - public uint spt; - // It stats on cylinders, above, but, don't care - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024 - 12)] - public byte[] parentFileName; - public uint parentGeneration; - public uint generation; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)] - public byte[] name; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] - public byte[] description; - public uint savedGeneration; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] reserved; - [MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 396)] - public byte[] padding; - } - - struct VMwareExtent - { - public string Access; - public uint Sectors; - public string Type; - public IFilter Filter; - public string Filename; - public uint Offset; - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware/Structs.cs b/DiscImageChef.DiscImages/VMware/Structs.cs new file mode 100644 index 000000000..db8f5daa8 --- /dev/null +++ b/DiscImageChef.DiscImages/VMware/Structs.cs @@ -0,0 +1,107 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for VMware disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class VMware + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VMwareExtentHeader + { + public uint magic; + public uint version; + public uint flags; + public ulong capacity; + public ulong grainSize; + public ulong descriptorOffset; + public ulong descriptorSize; + public uint GTEsPerGT; + public ulong rgdOffset; + public ulong gdOffset; + public ulong overhead; + [MarshalAs(UnmanagedType.U1)] + public bool uncleanShutdown; + public byte singleEndLineChar; + public byte nonEndLineChar; + public byte doubleEndLineChar1; + public byte doubleEndLineChar2; + public ushort compression; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 433)] + public byte[] padding; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VMwareCowHeader + { + public uint magic; + public uint version; + public uint flags; + public uint sectors; + public uint grainSize; + public uint gdOffset; + public uint numGDEntries; + public uint freeSector; + public uint cylinders; + public uint heads; + public uint spt; + // It stats on cylinders, above, but, don't care + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024 - 12)] + public byte[] parentFileName; + public uint parentGeneration; + public uint generation; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)] + public byte[] name; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] + public byte[] description; + public uint savedGeneration; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] reserved; + [MarshalAs(UnmanagedType.U1)] + public bool uncleanShutdown; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 396)] + public byte[] padding; + } + + struct VMwareExtent + { + public string Access; + public uint Sectors; + public string Type; + public IFilter Filter; + public string Filename; + public uint Offset; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware/Unsupported.cs b/DiscImageChef.DiscImages/VMware/Unsupported.cs new file mode 100644 index 000000000..2e4cbd7f0 --- /dev/null +++ b/DiscImageChef.DiscImages/VMware/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by VMware disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class VMware + { + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware/VMware.cs b/DiscImageChef.DiscImages/VMware/VMware.cs new file mode 100644 index 000000000..e6a9358e6 --- /dev/null +++ b/DiscImageChef.DiscImages/VMware/VMware.cs @@ -0,0 +1,93 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : VMware.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages VMware disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class VMware : IWritableImage + { + string adapter_type; + uint cid; + StreamWriter descriptorStream; + Dictionary<ulong, VMwareExtent> extents; + IFilter gdFilter; + Dictionary<ulong, byte[]> grainCache; + ulong grainSize; + uint[] gTable; + bool hasParent; + uint hwversion; + ImageInfo imageInfo; + string imageType; + uint maxCachedGrains; + uint parentCid; + IMediaImage parentImage; + string parentName; + Dictionary<ulong, byte[]> sectorCache; + uint version; + VMwareCowHeader vmCHdr; + VMwareExtentHeader vmEHdr; + string writingBaseName; + FileStream writingStream; + + public VMware() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = "VMware", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/VMware/Write.cs b/DiscImageChef.DiscImages/VMware/Write.cs new file mode 100644 index 000000000..bd8f49bbe --- /dev/null +++ b/DiscImageChef.DiscImages/VMware/Write.cs @@ -0,0 +1,343 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes VMware disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class VMware + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(options != null) + { + if(options.TryGetValue("adapter", out adapter_type)) + switch(adapter_type.ToLowerInvariant()) + { + case "ide": + case "lsilogic": + case "buslogic": + adapter_type = adapter_type.ToLowerInvariant(); + break; + case "legacyesx": + adapter_type = "legacyESX"; + break; + default: + ErrorMessage = $"Invalid adapter type {adapter_type}"; + return false; + } + else adapter_type = "ide"; + + if(options.TryGetValue("hwversion", out string tmpValue)) + { + if(!uint.TryParse(tmpValue, out hwversion)) + { + ErrorMessage = "Invalid value for hwversion option"; + return false; + } + } + else hwversion = 4; + + if(options.TryGetValue("split", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out bool tmpBool)) + { + ErrorMessage = "Invalid value for split option"; + return false; + } + + if(tmpBool) + { + ErrorMessage = "Splitted images not yet implemented"; + return false; + } + } + + if(options.TryGetValue("sparse", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out bool tmpBool)) + { + ErrorMessage = "Invalid value for sparse option"; + return false; + } + + if(tmpBool) + { + ErrorMessage = "Sparse images not yet implemented"; + return false; + } + } + } + else + { + adapter_type = "ide"; + hwversion = 4; + } + + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try + { + writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); + descriptorStream = new StreamWriter(path, false, Encoding.ASCII); + // TODO: Support split + writingStream = new FileStream(writingBaseName + "-flat.vmdk", FileMode.OpenOrCreate, + FileAccess.ReadWrite, FileShare.None); + } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + // TODO: Implement sparse and split + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + // TODO: Implement sparse and split + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + writingStream.Flush(); + writingStream.Close(); + + if(imageInfo.Cylinders == 0) + { + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + while(imageInfo.Cylinders == 0) + { + imageInfo.Heads--; + + if(imageInfo.Heads == 0) + { + imageInfo.SectorsPerTrack--; + imageInfo.Heads = 16; + } + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); + + if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; + } + } + + descriptorStream.WriteLine("# Disk DescriptorFile"); + descriptorStream.WriteLine("version=1"); + descriptorStream.WriteLine($"CID={new Random().Next(int.MinValue, int.MaxValue):x8}"); + descriptorStream.WriteLine("parentCID=ffffffff"); + descriptorStream.WriteLine("createType=\"monolithicFlat\""); + descriptorStream.WriteLine(); + descriptorStream.WriteLine("# Extent description"); + descriptorStream.WriteLine($"RW {imageInfo.Sectors} FLAT \"{writingBaseName + "-flat.vmdk"}\" 0"); + descriptorStream.WriteLine(); + descriptorStream.WriteLine("# The Disk Data Base"); + descriptorStream.WriteLine("#DDB"); + descriptorStream.WriteLine(); + descriptorStream.WriteLine($"ddb.virtualHWVersion = \"{hwversion}\""); + descriptorStream.WriteLine($"ddb.geometry.cylinders = \"{imageInfo.Cylinders}\""); + descriptorStream.WriteLine($"ddb.geometry.heads = \"{imageInfo.Heads}\""); + descriptorStream.WriteLine($"ddb.geometry.sectors = \"{imageInfo.SectorsPerTrack}\""); + descriptorStream.WriteLine($"ddb.adapterType = \"{adapter_type}\""); + + descriptorStream.Flush(); + descriptorStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > ushort.MaxValue) + { + ErrorMessage = "Too many cylinders."; + return false; + } + + if(heads > byte.MaxValue) + { + ErrorMessage = "Too many heads."; + return false; + } + + if(sectorsPerTrack > byte.MaxValue) + { + ErrorMessage = "Too many sectors per track."; + return false; + } + + imageInfo.SectorsPerTrack = sectorsPerTrack; + imageInfo.Heads = heads; + imageInfo.Cylinders = cylinders; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98.cs b/DiscImageChef.DiscImages/Virtual98.cs deleted file mode 100644 index d1be8de92..000000000 --- a/DiscImageChef.DiscImages/Virtual98.cs +++ /dev/null @@ -1,574 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : Virtual98.cs -// Author(s) : Natalia Portillo <claunia@claunia.com> -// -// Component : Disk image plugins. -// -// --[ Description ] ---------------------------------------------------------- -// -// Manages Virtual98 disk images. -// -// --[ License ] -------------------------------------------------------------- -// -// This library is free software; you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation; either version 2.1 of the -// License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, see <http://www.gnu.org/licenses/>. -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.CommonTypes.Enums; -using DiscImageChef.CommonTypes.Exceptions; -using DiscImageChef.CommonTypes.Interfaces; -using DiscImageChef.CommonTypes.Structs; -using DiscImageChef.Console; -using Schemas; - -namespace DiscImageChef.DiscImages -{ - // Info from Neko Project II emulator - public class Virtual98 : IWritableImage - { - readonly byte[] signature = {0x56, 0x48, 0x44, 0x31, 0x2E, 0x30, 0x30, 0x00}; - ImageInfo imageInfo; - IFilter nhdImageFilter; - - Virtual98Header v98Hdr; - FileStream writingStream; - - public Virtual98() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public ImageInfo Info => imageInfo; - - public string Name => "Virtual98 Disk Image"; - public Guid Id => new Guid("C0CDE13D-04D0-4913-8740-AFAA44D0A107"); - - public string Format => "Virtual98 disk image"; - - public List<Partition> Partitions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Track> Tracks => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public List<Session> Sessions => - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - public bool Identify(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p - Encoding shiftjis = Encoding.GetEncoding("shift_jis"); - - v98Hdr = new Virtual98Header(); - - if(stream.Length < Marshal.SizeOf(v98Hdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(v98Hdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - v98Hdr = (Virtual98Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Virtual98Header)); - handle.Free(); - - if(!v98Hdr.signature.SequenceEqual(signature)) return false; - - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.signature = \"{0}\"", - StringHandlers.CToString(v98Hdr.signature, shiftjis)); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.comment = \"{0}\"", - StringHandlers.CToString(v98Hdr.comment, shiftjis)); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.padding = {0}", v98Hdr.padding); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.mbsize = {0}", v98Hdr.mbsize); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.sectorsize = {0}", v98Hdr.sectorsize); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.sectors = {0}", v98Hdr.sectors); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.surfaces = {0}", v98Hdr.surfaces); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.cylinders = {0}", v98Hdr.cylinders); - DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.totals = {0}", v98Hdr.totals); - - return true; - } - - public bool Open(IFilter imageFilter) - { - Stream stream = imageFilter.GetDataForkStream(); - stream.Seek(0, SeekOrigin.Begin); - // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p - Encoding shiftjis = Encoding.GetEncoding("shift_jis"); - - v98Hdr = new Virtual98Header(); - - if(stream.Length < Marshal.SizeOf(v98Hdr)) return false; - - byte[] hdrB = new byte[Marshal.SizeOf(v98Hdr)]; - stream.Read(hdrB, 0, hdrB.Length); - - GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); - v98Hdr = (Virtual98Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Virtual98Header)); - handle.Free(); - - imageInfo.MediaType = MediaType.GENERIC_HDD; - - imageInfo.ImageSize = (ulong)(stream.Length - 0xDC); - imageInfo.CreationTime = imageFilter.GetCreationTime(); - imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.Sectors = v98Hdr.totals; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.SectorSize = v98Hdr.sectorsize; - imageInfo.Cylinders = v98Hdr.cylinders; - imageInfo.Heads = v98Hdr.surfaces; - imageInfo.SectorsPerTrack = v98Hdr.sectors; - imageInfo.Comments = StringHandlers.CToString(v98Hdr.comment, shiftjis); - - nhdImageFilter = imageFilter; - - return true; - } - - public byte[] ReadSector(ulong sectorAddress) - { - return ReadSectors(sectorAddress, 1); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length) - { - if(sectorAddress > imageInfo.Sectors - 1) - throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); - - if(sectorAddress + length > imageInfo.Sectors) - throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); - - byte[] buffer = new byte[length * imageInfo.SectorSize]; - - Stream stream = nhdImageFilter.GetDataForkStream(); - - // V98 are lazy allocated - if((long)(0xDC + sectorAddress * imageInfo.SectorSize) >= stream.Length) return buffer; - - stream.Seek((long)(0xDC + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - - int toRead = (int)(length * imageInfo.SectorSize); - - if(toRead + stream.Position > stream.Length) toRead = (int)(stream.Length - stream.Position); - - stream.Read(buffer, 0, toRead); - - return buffer; - } - - public byte[] ReadDiskTag(MediaTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorLong(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(Session session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public List<Track> GetSessionTracks(ushort session) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySector(ulong sectorAddress) - { - return null; - } - - public bool? VerifySector(ulong sectorAddress, uint track) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - failingLbas = new List<ulong>(); - unknownLbas = new List<ulong>(); - for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); - - return null; - } - - public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, - out List<ulong> unknownLbas) - { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - } - - public bool? VerifyMediaImage() - { - return null; - } - - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata => null; - - public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes => - new[] - { - MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, - MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, - MediaType.PCCardTypeIV - }; - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => new[] {".v98"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize != 512) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(sectors > uint.MaxValue) - { - ErrorMessage = "Too many sectors"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - ErrorMessage = "Writing media tags is not supported."; - return false; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != 512) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(0xDC + sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % 512 != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(0xDC + sectorAddress * 512), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - if(imageInfo.Cylinders == 0) - { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; - imageInfo.SectorsPerTrack = 63; - - while(imageInfo.Cylinders == 0) - { - imageInfo.Heads--; - - if(imageInfo.Heads == 0) - { - imageInfo.SectorsPerTrack--; - imageInfo.Heads = 16; - } - - imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); - - if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; - } - } - - byte[] commentsBytes = null; - if(!string.IsNullOrEmpty(imageInfo.Comments)) - commentsBytes = Encoding.GetEncoding("shift_jis").GetBytes(imageInfo.Comments); - - v98Hdr = new Virtual98Header - { - comment = new byte[128], - cylinders = (ushort)imageInfo.Cylinders, - padding2 = new byte[0x44], - sectors = (byte)imageInfo.SectorsPerTrack, - sectorsize = (ushort)imageInfo.SectorSize, - signature = signature, - surfaces = (byte)imageInfo.Heads, - totals = (uint)imageInfo.Sectors - }; - - if(commentsBytes != null) - Array.Copy(commentsBytes, 0, v98Hdr.comment, 0, - commentsBytes.Length >= 128 ? 128 : commentsBytes.Length); - - byte[] hdr = new byte[Marshal.SizeOf(v98Hdr)]; - IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(v98Hdr)); - Marshal.StructureToPtr(v98Hdr, hdrPtr, true); - Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); - Marshal.FreeHGlobal(hdrPtr); - - writingStream.Seek(0, SeekOrigin.Begin); - writingStream.Write(hdr, 0, hdr.Length); - - writingStream.Flush(); - writingStream.Close(); - - IsWriting = false; - ErrorMessage = ""; - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - imageInfo.Comments = metadata.Comments; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - if(cylinders > ushort.MaxValue) - { - ErrorMessage = "Too many cylinders."; - return false; - } - - if(heads > byte.MaxValue) - { - ErrorMessage = "Too many heads."; - return false; - } - - if(sectorsPerTrack > byte.MaxValue) - { - ErrorMessage = "Too many sectors per track."; - return false; - } - - imageInfo.SectorsPerTrack = sectorsPerTrack; - imageInfo.Heads = heads; - imageInfo.Cylinders = cylinders; - - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct Virtual98Header - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] signature; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] - public byte[] comment; - public uint padding; - public ushort mbsize; - public ushort sectorsize; - public byte sectors; - public byte surfaces; - public ushort cylinders; - public uint totals; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x44)] - public byte[] padding2; - } - } -} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Constants.cs b/DiscImageChef.DiscImages/Virtual98/Constants.cs new file mode 100644 index 000000000..0f0a12519 --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Constants.cs @@ -0,0 +1,39 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +namespace DiscImageChef.DiscImages +{ + public partial class Virtual98 + { + readonly byte[] signature = {0x56, 0x48, 0x44, 0x31, 0x2E, 0x30, 0x30, 0x00}; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Identify.cs b/DiscImageChef.DiscImages/Virtual98/Identify.cs new file mode 100644 index 000000000..e6bc5bd26 --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Identify.cs @@ -0,0 +1,79 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public partial class Virtual98 + { + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p + Encoding shiftjis = Encoding.GetEncoding("shift_jis"); + + v98Hdr = new Virtual98Header(); + + if(stream.Length < Marshal.SizeOf(v98Hdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(v98Hdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + v98Hdr = (Virtual98Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Virtual98Header)); + handle.Free(); + + if(!v98Hdr.signature.SequenceEqual(signature)) return false; + + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.signature = \"{0}\"", + StringHandlers.CToString(v98Hdr.signature, shiftjis)); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.comment = \"{0}\"", + StringHandlers.CToString(v98Hdr.comment, shiftjis)); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.padding = {0}", v98Hdr.padding); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.mbsize = {0}", v98Hdr.mbsize); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.sectorsize = {0}", v98Hdr.sectorsize); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.sectors = {0}", v98Hdr.sectors); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.surfaces = {0}", v98Hdr.surfaces); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.cylinders = {0}", v98Hdr.cylinders); + DicConsole.DebugWriteLine("Virtual98 plugin", "v98hdr.totals = {0}", v98Hdr.totals); + + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Properties.cs b/DiscImageChef.DiscImages/Virtual98/Properties.cs new file mode 100644 index 000000000..36505858c --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Properties.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Virtual98 + { + public ImageInfo Info => imageInfo; + + public string Name => "Virtual98 Disk Image"; + public Guid Id => new Guid("C0CDE13D-04D0-4913-8740-AFAA44D0A107"); + + public string Format => "Virtual98 disk image"; + + public List<Partition> Partitions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Track> Tracks => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + public List<Session> Sessions => + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata => null; + public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes => + new[] + { + MediaType.GENERIC_HDD, MediaType.Unknown, MediaType.FlashDrive, MediaType.CompactFlash, + MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII, + MediaType.PCCardTypeIV + }; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => new[] {".v98"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Read.cs b/DiscImageChef.DiscImages/Virtual98/Read.cs new file mode 100644 index 000000000..7411ebd77 --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Read.cs @@ -0,0 +1,113 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class Virtual98 + { + public bool Open(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + // Even if comment is supposedly ASCII, I'm pretty sure most emulators allow Shift-JIS to be used :p + Encoding shiftjis = Encoding.GetEncoding("shift_jis"); + + v98Hdr = new Virtual98Header(); + + if(stream.Length < Marshal.SizeOf(v98Hdr)) return false; + + byte[] hdrB = new byte[Marshal.SizeOf(v98Hdr)]; + stream.Read(hdrB, 0, hdrB.Length); + + GCHandle handle = GCHandle.Alloc(hdrB, GCHandleType.Pinned); + v98Hdr = (Virtual98Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Virtual98Header)); + handle.Free(); + + imageInfo.MediaType = MediaType.GENERIC_HDD; + + imageInfo.ImageSize = (ulong)(stream.Length - 0xDC); + imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.Sectors = v98Hdr.totals; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.SectorSize = v98Hdr.sectorsize; + imageInfo.Cylinders = v98Hdr.cylinders; + imageInfo.Heads = v98Hdr.surfaces; + imageInfo.SectorsPerTrack = v98Hdr.sectors; + imageInfo.Comments = StringHandlers.CToString(v98Hdr.comment, shiftjis); + + nhdImageFilter = imageFilter; + + return true; + } + + public byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > imageInfo.Sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > imageInfo.Sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * imageInfo.SectorSize]; + + Stream stream = nhdImageFilter.GetDataForkStream(); + + // V98 are lazy allocated + if((long)(0xDC + sectorAddress * imageInfo.SectorSize) >= stream.Length) return buffer; + + stream.Seek((long)(0xDC + sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + + int toRead = (int)(length * imageInfo.SectorSize); + + if(toRead + stream.Position > stream.Length) toRead = (int)(stream.Length - stream.Position); + + stream.Read(buffer, 0, toRead); + + return buffer; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Structs.cs b/DiscImageChef.DiscImages/Virtual98/Structs.cs new file mode 100644 index 000000000..f15e0c019 --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Structs.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace DiscImageChef.DiscImages +{ + public partial class Virtual98 + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Virtual98Header + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] signature; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] comment; + public uint padding; + public ushort mbsize; + public ushort sectorsize; + public byte sectors; + public byte surfaces; + public ushort cylinders; + public uint totals; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x44)] + public byte[] padding2; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Unsupported.cs b/DiscImageChef.DiscImages/Virtual98/Unsupported.cs new file mode 100644 index 000000000..5d8393ac6 --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Unsupported.cs @@ -0,0 +1,138 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class Virtual98 + { + public byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public List<Track> GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + failingLbas = new List<ulong>(); + unknownLbas = new List<ulong>(); + for(ulong i = 0; i < imageInfo.Sectors; i++) unknownLbas.Add(i); + + return null; + } + + public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas, + out List<ulong> unknownLbas) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public bool? VerifyMediaImage() + { + return null; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Virtual98.cs b/DiscImageChef.DiscImages/Virtual98/Virtual98.cs new file mode 100644 index 000000000..83a315a72 --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Virtual98.cs @@ -0,0 +1,76 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Virtual98.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + // Info from Neko Project II emulator + public partial class Virtual98 : IWritableImage + { + ImageInfo imageInfo; + IFilter nhdImageFilter; + Virtual98Header v98Hdr; + FileStream writingStream; + + public Virtual98() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Virtual98/Write.cs b/DiscImageChef.DiscImages/Virtual98/Write.cs new file mode 100644 index 000000000..9976c22df --- /dev/null +++ b/DiscImageChef.DiscImages/Virtual98/Write.cs @@ -0,0 +1,285 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes Virtual98 disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class Virtual98 + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(sectors > uint.MaxValue) + { + ErrorMessage = "Too many sectors"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(0xDC + sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(0xDC + sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + if(imageInfo.Cylinders == 0) + { + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + while(imageInfo.Cylinders == 0) + { + imageInfo.Heads--; + + if(imageInfo.Heads == 0) + { + imageInfo.SectorsPerTrack--; + imageInfo.Heads = 16; + } + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); + + if(imageInfo.Cylinders == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; + } + } + + byte[] commentsBytes = null; + if(!string.IsNullOrEmpty(imageInfo.Comments)) + commentsBytes = Encoding.GetEncoding("shift_jis").GetBytes(imageInfo.Comments); + + v98Hdr = new Virtual98Header + { + comment = new byte[128], + cylinders = (ushort)imageInfo.Cylinders, + padding2 = new byte[0x44], + sectors = (byte)imageInfo.SectorsPerTrack, + sectorsize = (ushort)imageInfo.SectorSize, + signature = signature, + surfaces = (byte)imageInfo.Heads, + totals = (uint)imageInfo.Sectors + }; + + if(commentsBytes != null) + Array.Copy(commentsBytes, 0, v98Hdr.comment, 0, + commentsBytes.Length >= 128 ? 128 : commentsBytes.Length); + + byte[] hdr = new byte[Marshal.SizeOf(v98Hdr)]; + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(v98Hdr)); + Marshal.StructureToPtr(v98Hdr, hdrPtr, true); + Marshal.Copy(hdrPtr, hdr, 0, hdr.Length); + Marshal.FreeHGlobal(hdrPtr); + + writingStream.Seek(0, SeekOrigin.Begin); + writingStream.Write(hdr, 0, hdr.Length); + + writingStream.Flush(); + writingStream.Close(); + + IsWriting = false; + ErrorMessage = ""; + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + imageInfo.Comments = metadata.Comments; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + if(cylinders > ushort.MaxValue) + { + ErrorMessage = "Too many cylinders."; + return false; + } + + if(heads > byte.MaxValue) + { + ErrorMessage = "Too many heads."; + return false; + } + + if(sectorsPerTrack > byte.MaxValue) + { + ErrorMessage = "Too many sectors per track."; + return false; + } + + imageInfo.SectorsPerTrack = sectorsPerTrack; + imageInfo.Heads = heads; + imageInfo.Cylinders = cylinders; + + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ZZZRawImage/Constants.cs b/DiscImageChef.DiscImages/ZZZRawImage/Constants.cs new file mode 100644 index 000000000..871228e6b --- /dev/null +++ b/DiscImageChef.DiscImages/ZZZRawImage/Constants.cs @@ -0,0 +1,68 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for raw image, that is, user data sector by sector copy. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes.Enums; + +namespace DiscImageChef.DiscImages +{ + public partial class ZZZRawImage + { + readonly byte[] cdSync = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00}; + readonly (MediaTagType tag, string name)[] readWriteSidecars = + { + (MediaTagType.ATA_IDENTIFY, ".identify.bin"), (MediaTagType.BD_DI, ".di.bin"), + (MediaTagType.CD_ATIP, ".atip.bin"), (MediaTagType.CD_FullTOC, ".toc.bin"), + (MediaTagType.CD_LeadIn, ".leadin.bin"), (MediaTagType.CD_PMA, ".pma.bin"), + (MediaTagType.CD_TEXT, ".cdtext.bin"), (MediaTagType.DCB, ".dcb.bin"), (MediaTagType.DVD_ADIP, ".adip.bin"), + (MediaTagType.DVD_BCA, ".bca.bin"), (MediaTagType.DVD_CMI, ".cmi.bin"), (MediaTagType.DVD_DMI, ".dmi.bin"), + (MediaTagType.DVD_MediaIdentifier, ".mid.bin"), (MediaTagType.DVD_PFI, ".pfi.bin"), + (MediaTagType.DVDRAM_DDS, ".dds.bin"), (MediaTagType.DVDRAM_SpareArea, ".sai.bin"), + (MediaTagType.DVDR_PFI, ".pfir.bin"), (MediaTagType.DVDR_PreRecordedInfo, ".pri.bin"), + (MediaTagType.Floppy_LeadOut, ".leadout.bin"), (MediaTagType.HDDVD_CPI, ".cpi.bin"), + (MediaTagType.MMC_ExtendedCSD, ".ecsd.bin"), (MediaTagType.PCMCIA_CIS, ".cis.bin"), + (MediaTagType.SCSI_INQUIRY, ".inquiry.bin"), (MediaTagType.SCSI_MODEPAGE_2A, ".modepage2a.bin"), + (MediaTagType.SCSI_MODESENSE_10, ".modesense10.bin"), (MediaTagType.SCSI_MODESENSE_6, ".modesense.bin"), + (MediaTagType.SD_CID, ".cid.bin"), (MediaTagType.SD_CSD, ".csd.bin"), (MediaTagType.SD_OCR, ".ocr.bin"), + (MediaTagType.SD_SCR, ".scr.bin"), (MediaTagType.USB_Descriptors, ".usbdescriptors.bin"), + (MediaTagType.Xbox_DMI, ".xboxdmi.bin"), (MediaTagType.Xbox_PFI, ".xboxpfi.bin"), + (MediaTagType.Xbox_SecuritySector, ".ss.bin") + }; + + readonly (MediaTagType tag, string name)[] writeOnlySidecars = + { + (MediaTagType.ATAPI_IDENTIFY, ".identify.bin"), (MediaTagType.BD_BCA, ".bca.bin"), + (MediaTagType.BD_DDS, ".dds.bin"), (MediaTagType.BD_DI, ".di.bin"), (MediaTagType.BD_SpareArea, ".sai.bin"), + (MediaTagType.CD_LeadOut, ".leadout.bin"), (MediaTagType.MMC_CID, ".cid.bin"), + (MediaTagType.MMC_CSD, ".csd.bin"), (MediaTagType.MMC_OCR, ".ocr.bin") + }; + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ZZZRawImage/Helpers.cs b/DiscImageChef.DiscImages/ZZZRawImage/Helpers.cs new file mode 100644 index 000000000..d5f836199 --- /dev/null +++ b/DiscImageChef.DiscImages/ZZZRawImage/Helpers.cs @@ -0,0 +1,155 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Helpers.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains helpers for raw image, that is, user data sector by sector copy. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using DiscImageChef.CommonTypes; + +namespace DiscImageChef.DiscImages +{ + public partial class ZZZRawImage + { + MediaType CalculateDiskType() + { + if(imageInfo.SectorSize == 2048) + { + if(imageInfo.Sectors <= 360000) return MediaType.CD; + if(imageInfo.Sectors <= 2295104) return MediaType.DVDPR; + if(imageInfo.Sectors <= 2298496) return MediaType.DVDR; + if(imageInfo.Sectors <= 4171712) return MediaType.DVDRDL; + if(imageInfo.Sectors <= 4173824) return MediaType.DVDPRDL; + if(imageInfo.Sectors <= 24438784) return MediaType.BDR; + + return imageInfo.Sectors <= 62500864 ? MediaType.BDRXL : MediaType.Unknown; + } + + switch(imageInfo.ImageSize) + { + case 80384: return MediaType.ECMA_66; + case 81664: return MediaType.IBM23FD; + case 92160: return MediaType.ATARI_525_SD; + case 102400: return MediaType.ACORN_525_SS_SD_40; + case 116480: return MediaType.Apple32SS; + case 133120: return MediaType.ATARI_525_ED; + case 143360: return MediaType.Apple33SS; + case 163840: + if(imageInfo.SectorSize == 256) return MediaType.ACORN_525_SS_DD_40; + + return MediaType.DOS_525_SS_DD_8; + case 184320: return MediaType.DOS_525_SS_DD_9; + case 204800: return MediaType.ACORN_525_SS_SD_80; + case 232960: return MediaType.Apple32DS; + case 242944: return MediaType.IBM33FD_128; + case 256256: return MediaType.ECMA_54; + case 286720: return MediaType.Apple33DS; + case 287488: return MediaType.IBM33FD_256; + case 306432: return MediaType.IBM33FD_512; + case 322560: return MediaType.Apricot_35; + case 325632: return MediaType.ECMA_70; + case 327680: + if(imageInfo.SectorSize == 256) return MediaType.ACORN_525_SS_DD_80; + + return MediaType.DOS_525_DS_DD_8; + case 368640: + if(extension == ".st") return MediaType.DOS_35_SS_DD_9; + + return MediaType.DOS_525_DS_DD_9; + case 409600: + if(extension == ".st") return MediaType.ATARI_35_SS_DD; + + return MediaType.AppleSonySS; + case 450560: return MediaType.ATARI_35_SS_DD_11; + case 495872: return MediaType.IBM43FD_128; + case 512512: return MediaType.ECMA_59; + case 653312: return MediaType.ECMA_78; + case 655360: return MediaType.ACORN_525_DS_DD; + case 737280: return MediaType.DOS_35_DS_DD_9; + case 819200: + if(imageInfo.SectorSize == 256) return MediaType.CBM_35_DD; + if((extension == ".adf" || extension == ".adl") && imageInfo.SectorSize == 1024) + return MediaType.ACORN_35_DS_DD; + if(extension == ".st") return MediaType.ATARI_35_DS_DD; + + return MediaType.AppleSonyDS; + case 839680: return MediaType.FDFORMAT_35_DD; + case 901120: + if(extension == ".st") return MediaType.ATARI_35_DS_DD_11; + + return MediaType.CBM_AMIGA_35_DD; + case 988416: return MediaType.IBM43FD_256; + case 995072: return MediaType.IBM53FD_256; + case 1021696: return MediaType.ECMA_99_26; + case 1146624: return MediaType.IBM53FD_512; + case 1177344: return MediaType.ECMA_99_15; + case 1222400: return MediaType.IBM53FD_1024; + case 1228800: return MediaType.DOS_525_HD; + case 1255168: return MediaType.ECMA_69_8; + case 1261568: return MediaType.NEC_525_HD; + case 1304320: return MediaType.ECMA_99_8; + case 1427456: return MediaType.FDFORMAT_525_HD; + case 1474560: return MediaType.DOS_35_HD; + case 1638400: return MediaType.ACORN_35_DS_HD; + case 1720320: return MediaType.DMF; + case 1763328: return MediaType.FDFORMAT_35_HD; + case 1802240: return MediaType.CBM_AMIGA_35_HD; + case 1880064: return MediaType.XDF_35; + case 1884160: return MediaType.XDF_35; + case 2949120: return MediaType.DOS_35_ED; + case 9338880: return MediaType.NEC_35_TD; + case 20818944: return MediaType.Floptical; + case 33554432: return MediaType.FD32MB; + case 40387584: return MediaType.PocketZip; + case 100663296: return MediaType.ZIP100; + case 126222336: return MediaType.LS120; + case 127923200: return MediaType.ECMA_154; + case 201410560: return MediaType.HiFD; + case 229632000: return MediaType.ECMA_201; + case 240386048: return MediaType.LS240; + case 250640384: return MediaType.ZIP250; + case 481520640: return MediaType.ECMA_183_512; + case 533403648: return MediaType.ECMA_183; + case 596787200: return MediaType.ECMA_184_512; + case 654540800: return MediaType.ECMA_184; + case 1070617600: return MediaType.Jaz; + + #region Commodore + case 174848: + case 175531: return MediaType.CBM_1540; + case 196608: + case 197376: return MediaType.CBM_1540_Ext; + case 349696: + case 351062: return MediaType.CBM_1571; + #endregion Commodore + + default: return MediaType.GENERIC_HDD; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ZZZRawImage/Identify.cs b/DiscImageChef.DiscImages/ZZZRawImage/Identify.cs new file mode 100644 index 000000000..af60c974d --- /dev/null +++ b/DiscImageChef.DiscImages/ZZZRawImage/Identify.cs @@ -0,0 +1,91 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies raw image, that is, user data sector by sector copy. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; + +namespace DiscImageChef.DiscImages +{ + public partial class ZZZRawImage + { + public bool Identify(IFilter imageFilter) + { + // Check if file is not multiple of 512 + if(imageFilter.GetDataForkLength() % 512 == 0) return true; + + extension = Path.GetExtension(imageFilter.GetFilename())?.ToLower(); + + if(extension == ".hdf" && imageFilter.GetDataForkLength() % 256 == 0) return true; + + // Only for single track data CDs + if(imageFilter.GetDataForkLength() % 2352 == 0 && imageFilter.GetDataForkLength() <= 846720000 || + imageFilter.GetDataForkLength() % 2448 == 0 && imageFilter.GetDataForkLength() <= 881280000) + { + byte[] sync = new byte[12]; + Stream stream = imageFilter.GetDataForkStream(); + stream.Position = 0; + stream.Read(sync, 0, 12); + return cdSync.SequenceEqual(sync); + } + + // Check known disk sizes with sectors smaller than 512 + switch(imageFilter.GetDataForkLength()) + { + #region Commodore + case 174848: + case 175531: + case 197376: + case 351062: + case 822400: + #endregion Commodore + + case 81664: + case 116480: + case 242944: + case 256256: + case 287488: + case 306432: + case 495872: + case 988416: + case 995072: + case 1021696: + case 1146624: + case 1177344: + case 1222400: + case 1304320: + case 1255168: return true; + default: return false; + } + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ZZZRawImage/Properties.cs b/DiscImageChef.DiscImages/ZZZRawImage/Properties.cs new file mode 100644 index 000000000..1265387da --- /dev/null +++ b/DiscImageChef.DiscImages/ZZZRawImage/Properties.cs @@ -0,0 +1,171 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for raw image, that is, user data sector by sector copy. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Exceptions; +using DiscImageChef.CommonTypes.Structs; +using Schemas; +using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; + +namespace DiscImageChef.DiscImages +{ + public partial class ZZZRawImage + { + public string Name => "Raw Disk Image"; + // Non-random UUID to recognize this specific plugin + public Guid Id => new Guid("12345678-AAAA-BBBB-CCCC-123456789000"); + public ImageInfo Info => imageInfo; + + public string Format => "Raw disk image (sector by sector copy)"; + + public List<Track> Tracks + { + get + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + Track trk = new Track + { + TrackBytesPerSector = rawCompactDisc ? (mode2 ? 2336 : 2048) : (int)imageInfo.SectorSize, + TrackEndSector = imageInfo.Sectors - 1, + TrackFile = rawImageFilter.GetFilename(), + TrackFileOffset = 0, + TrackFileType = "BINARY", + TrackRawBytesPerSector = rawCompactDisc ? 2352 : (int)imageInfo.SectorSize, + TrackSequence = 1, + TrackStartSector = 0, + TrackSubchannelType = + hasSubchannel ? TrackSubchannelType.RawInterleaved : TrackSubchannelType.None, + TrackType = rawCompactDisc + ? (mode2 ? TrackType.CdMode2Formless : TrackType.CdMode1) + : TrackType.Data, + TrackSession = 1 + }; + List<Track> lst = new List<Track> {trk}; + return lst; + } + } + + public List<Session> Sessions + { + get + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + Session sess = new Session + { + EndSector = imageInfo.Sectors - 1, + EndTrack = 1, + SessionSequence = 1, + StartSector = 0, + StartTrack = 1 + }; + List<Session> lst = new List<Session> {sess}; + return lst; + } + } + + public List<Partition> Partitions + { + get + { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + + List<Partition> parts = new List<Partition>(); + Partition part = new Partition + { + Start = 0, + Length = imageInfo.Sectors, + Offset = 0, + Sequence = 0, + Type = rawCompactDisc ? (mode2 ? "MODE2/2352" : "MODE1/2352") : "MODE1/2048", + Size = imageInfo.Sectors * imageInfo.SectorSize + }; + parts.Add(part); + return parts; + } + } + public List<DumpHardwareType> DumpHardware => null; + public CICMMetadataType CicmMetadata { get; private set; } + public IEnumerable<MediaTagType> SupportedMediaTags => + readWriteSidecars.Concat(writeOnlySidecars).OrderBy(t => t.tag).Select(t => t.tag).ToArray(); + + public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable<MediaType> SupportedMediaTypes + { + get + { + List<MediaType> types = new List<MediaType>(); + foreach(MediaType type in Enum.GetValues(typeof(MediaType))) + switch(type) + { + // TODO: Implement support for writing formats with different track 0 bytes per sector + case MediaType.IBM33FD_256: + case MediaType.IBM33FD_512: + case MediaType.IBM43FD_128: + case MediaType.IBM43FD_256: + case MediaType.IBM53FD_256: + case MediaType.IBM53FD_512: + case MediaType.IBM53FD_1024: + case MediaType.ECMA_99_8: + case MediaType.ECMA_99_15: + case MediaType.ECMA_99_26: + case MediaType.ECMA_66: + case MediaType.ECMA_69_8: + case MediaType.ECMA_69_15: + case MediaType.ECMA_69_26: + case MediaType.ECMA_70: + case MediaType.ECMA_78: continue; + default: + types.Add(type); + break; + } + + return types; + } + } + + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new (string name, Type type, string description)[] { }; + public IEnumerable<string> KnownExtensions => + new[] {".adf", ".adl", ".d81", ".dsk", ".hdf", ".ima", ".img", ".iso", ".ssd", ".st"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ZZZRawImage.cs b/DiscImageChef.DiscImages/ZZZRawImage/Read.cs similarity index 72% rename from DiscImageChef.DiscImages/ZZZRawImage.cs rename to DiscImageChef.DiscImages/ZZZRawImage/Read.cs index e1d8c5906..2c4eb7b73 100644 --- a/DiscImageChef.DiscImages/ZZZRawImage.cs +++ b/DiscImageChef.DiscImages/ZZZRawImage/Read.cs @@ -1,15 +1,15 @@ -// /*************************************************************************** +// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // -// Filename : ZZZRawImage.cs +// Filename : Read.cs // Author(s) : Natalia Portillo <claunia@claunia.com> // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Manages raw image, that is, user data sector by sector copy. +// Reads raw image, that is, user data sector by sector copy. // // --[ License ] -------------------------------------------------------------- // @@ -42,11 +42,9 @@ using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Interfaces; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; -using DiscImageChef.Decoders.ATA; using DiscImageChef.Decoders.CD; using DiscImageChef.Decoders.DVD; using DiscImageChef.Decoders.SCSI; -using DiscImageChef.Filters; using Schemas; using DMI = DiscImageChef.Decoders.Xbox.DMI; using Session = DiscImageChef.CommonTypes.Structs.Session; @@ -54,203 +52,8 @@ using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { - public class ZZZRawImage : IWritableImage + public partial class ZZZRawImage { - readonly byte[] cdSync = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00}; - readonly (MediaTagType tag, string name)[] readWriteSidecars = - { - (MediaTagType.ATA_IDENTIFY, ".identify.bin"), (MediaTagType.BD_DI, ".di.bin"), - (MediaTagType.CD_ATIP, ".atip.bin"), (MediaTagType.CD_FullTOC, ".toc.bin"), - (MediaTagType.CD_LeadIn, ".leadin.bin"), (MediaTagType.CD_PMA, ".pma.bin"), - (MediaTagType.CD_TEXT, ".cdtext.bin"), (MediaTagType.DCB, ".dcb.bin"), (MediaTagType.DVD_ADIP, ".adip.bin"), - (MediaTagType.DVD_BCA, ".bca.bin"), (MediaTagType.DVD_CMI, ".cmi.bin"), (MediaTagType.DVD_DMI, ".dmi.bin"), - (MediaTagType.DVD_MediaIdentifier, ".mid.bin"), (MediaTagType.DVD_PFI, ".pfi.bin"), - (MediaTagType.DVDRAM_DDS, ".dds.bin"), (MediaTagType.DVDRAM_SpareArea, ".sai.bin"), - (MediaTagType.DVDR_PFI, ".pfir.bin"), (MediaTagType.DVDR_PreRecordedInfo, ".pri.bin"), - (MediaTagType.Floppy_LeadOut, ".leadout.bin"), (MediaTagType.HDDVD_CPI, ".cpi.bin"), - (MediaTagType.MMC_ExtendedCSD, ".ecsd.bin"), (MediaTagType.PCMCIA_CIS, ".cis.bin"), - (MediaTagType.SCSI_INQUIRY, ".inquiry.bin"), (MediaTagType.SCSI_MODEPAGE_2A, ".modepage2a.bin"), - (MediaTagType.SCSI_MODESENSE_10, ".modesense10.bin"), (MediaTagType.SCSI_MODESENSE_6, ".modesense.bin"), - (MediaTagType.SD_CID, ".cid.bin"), (MediaTagType.SD_CSD, ".csd.bin"), (MediaTagType.SD_OCR, ".ocr.bin"), - (MediaTagType.SD_SCR, ".scr.bin"), (MediaTagType.USB_Descriptors, ".usbdescriptors.bin"), - (MediaTagType.Xbox_DMI, ".xboxdmi.bin"), (MediaTagType.Xbox_PFI, ".xboxpfi.bin"), - (MediaTagType.Xbox_SecuritySector, ".ss.bin") - }; - - readonly (MediaTagType tag, string name)[] writeOnlySidecars = - { - (MediaTagType.ATAPI_IDENTIFY, ".identify.bin"), (MediaTagType.BD_BCA, ".bca.bin"), - (MediaTagType.BD_DDS, ".dds.bin"), (MediaTagType.BD_DI, ".di.bin"), (MediaTagType.BD_SpareArea, ".sai.bin"), - (MediaTagType.CD_LeadOut, ".leadout.bin"), (MediaTagType.MMC_CID, ".cid.bin"), - (MediaTagType.MMC_CSD, ".csd.bin"), (MediaTagType.MMC_OCR, ".ocr.bin") - }; - - string basepath; - bool differentTrackZeroSize; - string extension; - bool hasSubchannel; - ImageInfo imageInfo; - Dictionary<MediaTagType, byte[]> mediaTags; - bool mode2; - bool rawCompactDisc; - IFilter rawImageFilter; - FileStream writingStream; - - public ZZZRawImage() - { - imageInfo = new ImageInfo - { - ReadableSectorTags = new List<SectorTagType>(), - ReadableMediaTags = new List<MediaTagType>(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = null, - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, - DriveFirmwareRevision = null - }; - } - - public string Name => "Raw Disk Image"; - // Non-random UUID to recognize this specific plugin - public Guid Id => new Guid("12345678-AAAA-BBBB-CCCC-123456789000"); - public ImageInfo Info => imageInfo; - - public string Format => "Raw disk image (sector by sector copy)"; - - public List<Track> Tracks - { - get - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - Track trk = new Track - { - TrackBytesPerSector = rawCompactDisc ? (mode2 ? 2336 : 2048) : (int)imageInfo.SectorSize, - TrackEndSector = imageInfo.Sectors - 1, - TrackFile = rawImageFilter.GetFilename(), - TrackFileOffset = 0, - TrackFileType = "BINARY", - TrackRawBytesPerSector = rawCompactDisc ? 2352 : (int)imageInfo.SectorSize, - TrackSequence = 1, - TrackStartSector = 0, - TrackSubchannelType = - hasSubchannel ? TrackSubchannelType.RawInterleaved : TrackSubchannelType.None, - TrackType = rawCompactDisc - ? (mode2 ? TrackType.CdMode2Formless : TrackType.CdMode1) - : TrackType.Data, - TrackSession = 1 - }; - List<Track> lst = new List<Track> {trk}; - return lst; - } - } - - public List<Session> Sessions - { - get - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - Session sess = new Session - { - EndSector = imageInfo.Sectors - 1, - EndTrack = 1, - SessionSequence = 1, - StartSector = 0, - StartTrack = 1 - }; - List<Session> lst = new List<Session> {sess}; - return lst; - } - } - - public List<Partition> Partitions - { - get - { - if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) - throw new FeatureUnsupportedImageException("Feature not supported by image format"); - - List<Partition> parts = new List<Partition>(); - Partition part = new Partition - { - Start = 0, - Length = imageInfo.Sectors, - Offset = 0, - Sequence = 0, - Type = rawCompactDisc ? (mode2 ? "MODE2/2352" : "MODE1/2352") : "MODE1/2048", - Size = imageInfo.Sectors * imageInfo.SectorSize - }; - parts.Add(part); - return parts; - } - } - - public bool Identify(IFilter imageFilter) - { - // Check if file is not multiple of 512 - if(imageFilter.GetDataForkLength() % 512 == 0) return true; - - extension = Path.GetExtension(imageFilter.GetFilename())?.ToLower(); - - if(extension == ".hdf" && imageFilter.GetDataForkLength() % 256 == 0) return true; - - // Only for single track data CDs - if(imageFilter.GetDataForkLength() % 2352 == 0 && imageFilter.GetDataForkLength() <= 846720000 || - imageFilter.GetDataForkLength() % 2448 == 0 && imageFilter.GetDataForkLength() <= 881280000) - { - byte[] sync = new byte[12]; - Stream stream = imageFilter.GetDataForkStream(); - stream.Position = 0; - stream.Read(sync, 0, 12); - return cdSync.SequenceEqual(sync); - } - - // Check known disk sizes with sectors smaller than 512 - switch(imageFilter.GetDataForkLength()) - { - #region Commodore - case 174848: - case 175531: - case 197376: - case 351062: - case 822400: - #endregion Commodore - - case 81664: - case 116480: - case 242944: - case 256256: - case 287488: - case 306432: - case 495872: - case 988416: - case 995072: - case 1021696: - case 1146624: - case 1177344: - case 1222400: - case 1304320: - case 1255168: return true; - default: return false; - } - } - public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); @@ -1046,7 +849,7 @@ namespace DiscImageChef.DiscImages // It's ATA, check tags if(mediaTags.TryGetValue(MediaTagType.ATA_IDENTIFY, out byte[] identifyBuf)) { - Identify.IdentifyDevice? ataId = Decoders.ATA.Identify.Decode(identifyBuf); + Decoders.ATA.Identify.IdentifyDevice? ataId = Decoders.ATA.Identify.Decode(identifyBuf); if(ataId.HasValue) { imageInfo.MediaType = (ushort)ataId.Value.GeneralConfiguration == 0x848A @@ -1313,9 +1116,6 @@ namespace DiscImageChef.DiscImages return null; } - public List<DumpHardwareType> DumpHardware => null; - public CICMMetadataType CicmMetadata { get; private set; } - public List<Track> GetSessionTracks(Session session) { if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) @@ -1586,349 +1386,5 @@ namespace DiscImageChef.DiscImages return ReadSectorsTag(sectorAddress, length, tag); } - - public IEnumerable<MediaTagType> SupportedMediaTags => - readWriteSidecars.Concat(writeOnlySidecars).OrderBy(t => t.tag).Select(t => t.tag).ToArray(); - - public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { }; - public IEnumerable<MediaType> SupportedMediaTypes - { - get - { - List<MediaType> types = new List<MediaType>(); - foreach(MediaType type in Enum.GetValues(typeof(MediaType))) - switch(type) - { - // TODO: Implement support for writing formats with different track 0 bytes per sector - case MediaType.IBM33FD_256: - case MediaType.IBM33FD_512: - case MediaType.IBM43FD_128: - case MediaType.IBM43FD_256: - case MediaType.IBM53FD_256: - case MediaType.IBM53FD_512: - case MediaType.IBM53FD_1024: - case MediaType.ECMA_99_8: - case MediaType.ECMA_99_15: - case MediaType.ECMA_99_26: - case MediaType.ECMA_66: - case MediaType.ECMA_69_8: - case MediaType.ECMA_69_15: - case MediaType.ECMA_69_26: - case MediaType.ECMA_70: - case MediaType.ECMA_78: continue; - default: - types.Add(type); - break; - } - - return types; - } - } - - public IEnumerable<(string name, Type type, string description)> SupportedOptions => - new (string name, Type type, string description)[] { }; - public IEnumerable<string> KnownExtensions => - new[] {".adf", ".adl", ".d81", ".dsk", ".hdf", ".ima", ".img", ".iso", ".ssd", ".st"}; - public bool IsWriting { get; private set; } - public string ErrorMessage { get; private set; } - - public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, - uint sectorSize) - { - if(sectorSize == 0) - { - ErrorMessage = "Unsupported sector size"; - return false; - } - - if(!SupportedMediaTypes.Contains(mediaType)) - { - ErrorMessage = $"Unsupport media format {mediaType}"; - return false; - } - - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; - - try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } - catch(IOException e) - { - ErrorMessage = $"Could not create new image file, exception {e.Message}"; - return false; - } - - basepath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); - mediaTags = new Dictionary<MediaTagType, byte[]>(); - - IsWriting = true; - ErrorMessage = null; - return true; - } - - public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) - { - // Geometry is not stored in image - return true; - } - - public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) - { - ErrorMessage = "Unsupported feature"; - return false; - } - - public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) - { - // Not supported - return false; - } - - public bool SetCicmMetadata(CICMMetadataType metadata) - { - // Not supported - return false; - } - - public bool WriteMediaTag(byte[] data, MediaTagType tag) - { - if(!SupportedMediaTags.Contains(tag)) - { - ErrorMessage = $"Tried to write unsupported media tag {tag}."; - return false; - } - - if(mediaTags.ContainsKey(tag)) mediaTags.Remove(tag); - - mediaTags.Add(tag, data); - return true; - } - - public bool WriteSector(byte[] data, ulong sectorAddress) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length != imageInfo.SectorSize) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress >= imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) - { - if(!IsWriting) - { - ErrorMessage = "Tried to write on a non-writable image"; - return false; - } - - if(data.Length % imageInfo.SectorSize != 0) - { - ErrorMessage = "Incorrect data size"; - return false; - } - - if(sectorAddress + length > imageInfo.Sectors) - { - ErrorMessage = "Tried to write past image size"; - return false; - } - - writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); - writingStream.Write(data, 0, data.Length); - - ErrorMessage = ""; - return true; - } - - public bool WriteSectorLong(byte[] data, ulong sectorAddress) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) - { - ErrorMessage = "Writing sectors with tags is not supported."; - return false; - } - - public bool SetTracks(List<Track> tracks) - { - if(tracks.Count <= 1) return true; - - ErrorMessage = "This format supports only 1 track"; - return false; - } - - public bool Close() - { - if(!IsWriting) - { - ErrorMessage = "Image is not opened for writing"; - return false; - } - - writingStream.Flush(); - writingStream.Close(); - IsWriting = false; - - foreach(KeyValuePair<MediaTagType, byte[]> tag in mediaTags) - { - string suffix = readWriteSidecars.Concat(writeOnlySidecars).Where(t => t.tag == tag.Key) - .Select(t => t.name).FirstOrDefault(); - - if(suffix == null) continue; - - FileStream tagStream = - new FileStream(basepath + suffix, FileMode.Create, FileAccess.ReadWrite, FileShare.None); - tagStream.Write(tag.Value, 0, tag.Value.Length); - tagStream.Close(); - } - - return true; - } - - public bool SetMetadata(ImageInfo metadata) - { - return true; - } - - MediaType CalculateDiskType() - { - if(imageInfo.SectorSize == 2048) - { - if(imageInfo.Sectors <= 360000) return MediaType.CD; - if(imageInfo.Sectors <= 2295104) return MediaType.DVDPR; - if(imageInfo.Sectors <= 2298496) return MediaType.DVDR; - if(imageInfo.Sectors <= 4171712) return MediaType.DVDRDL; - if(imageInfo.Sectors <= 4173824) return MediaType.DVDPRDL; - if(imageInfo.Sectors <= 24438784) return MediaType.BDR; - - return imageInfo.Sectors <= 62500864 ? MediaType.BDRXL : MediaType.Unknown; - } - - switch(imageInfo.ImageSize) - { - case 80384: return MediaType.ECMA_66; - case 81664: return MediaType.IBM23FD; - case 92160: return MediaType.ATARI_525_SD; - case 102400: return MediaType.ACORN_525_SS_SD_40; - case 116480: return MediaType.Apple32SS; - case 133120: return MediaType.ATARI_525_ED; - case 143360: return MediaType.Apple33SS; - case 163840: - if(imageInfo.SectorSize == 256) return MediaType.ACORN_525_SS_DD_40; - - return MediaType.DOS_525_SS_DD_8; - case 184320: return MediaType.DOS_525_SS_DD_9; - case 204800: return MediaType.ACORN_525_SS_SD_80; - case 232960: return MediaType.Apple32DS; - case 242944: return MediaType.IBM33FD_128; - case 256256: return MediaType.ECMA_54; - case 286720: return MediaType.Apple33DS; - case 287488: return MediaType.IBM33FD_256; - case 306432: return MediaType.IBM33FD_512; - case 322560: return MediaType.Apricot_35; - case 325632: return MediaType.ECMA_70; - case 327680: - if(imageInfo.SectorSize == 256) return MediaType.ACORN_525_SS_DD_80; - - return MediaType.DOS_525_DS_DD_8; - case 368640: - if(extension == ".st") return MediaType.DOS_35_SS_DD_9; - - return MediaType.DOS_525_DS_DD_9; - case 409600: - if(extension == ".st") return MediaType.ATARI_35_SS_DD; - - return MediaType.AppleSonySS; - case 450560: return MediaType.ATARI_35_SS_DD_11; - case 495872: return MediaType.IBM43FD_128; - case 512512: return MediaType.ECMA_59; - case 653312: return MediaType.ECMA_78; - case 655360: return MediaType.ACORN_525_DS_DD; - case 737280: return MediaType.DOS_35_DS_DD_9; - case 819200: - if(imageInfo.SectorSize == 256) return MediaType.CBM_35_DD; - if((extension == ".adf" || extension == ".adl") && imageInfo.SectorSize == 1024) - return MediaType.ACORN_35_DS_DD; - if(extension == ".st") return MediaType.ATARI_35_DS_DD; - - return MediaType.AppleSonyDS; - case 839680: return MediaType.FDFORMAT_35_DD; - case 901120: - if(extension == ".st") return MediaType.ATARI_35_DS_DD_11; - - return MediaType.CBM_AMIGA_35_DD; - case 988416: return MediaType.IBM43FD_256; - case 995072: return MediaType.IBM53FD_256; - case 1021696: return MediaType.ECMA_99_26; - case 1146624: return MediaType.IBM53FD_512; - case 1177344: return MediaType.ECMA_99_15; - case 1222400: return MediaType.IBM53FD_1024; - case 1228800: return MediaType.DOS_525_HD; - case 1255168: return MediaType.ECMA_69_8; - case 1261568: return MediaType.NEC_525_HD; - case 1304320: return MediaType.ECMA_99_8; - case 1427456: return MediaType.FDFORMAT_525_HD; - case 1474560: return MediaType.DOS_35_HD; - case 1638400: return MediaType.ACORN_35_DS_HD; - case 1720320: return MediaType.DMF; - case 1763328: return MediaType.FDFORMAT_35_HD; - case 1802240: return MediaType.CBM_AMIGA_35_HD; - case 1880064: return MediaType.XDF_35; - case 1884160: return MediaType.XDF_35; - case 2949120: return MediaType.DOS_35_ED; - case 9338880: return MediaType.NEC_35_TD; - case 20818944: return MediaType.Floptical; - case 33554432: return MediaType.FD32MB; - case 40387584: return MediaType.PocketZip; - case 100663296: return MediaType.ZIP100; - case 126222336: return MediaType.LS120; - case 127923200: return MediaType.ECMA_154; - case 201410560: return MediaType.HiFD; - case 229632000: return MediaType.ECMA_201; - case 240386048: return MediaType.LS240; - case 250640384: return MediaType.ZIP250; - case 481520640: return MediaType.ECMA_183_512; - case 533403648: return MediaType.ECMA_183; - case 596787200: return MediaType.ECMA_184_512; - case 654540800: return MediaType.ECMA_184; - case 1070617600: return MediaType.Jaz; - - #region Commodore - case 174848: - case 175531: return MediaType.CBM_1540; - case 196608: - case 197376: return MediaType.CBM_1540_Ext; - case 349696: - case 351062: return MediaType.CBM_1571; - #endregion Commodore - - default: return MediaType.GENERIC_HDD; - } - } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ZZZRawImage/Write.cs b/DiscImageChef.DiscImages/ZZZRawImage/Write.cs new file mode 100644 index 000000000..be513c020 --- /dev/null +++ b/DiscImageChef.DiscImages/ZZZRawImage/Write.cs @@ -0,0 +1,228 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Write.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Writes raw image, that is, user data sector by sector copy. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Structs; +using Schemas; + +namespace DiscImageChef.DiscImages +{ + public partial class ZZZRawImage + { + public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors, + uint sectorSize) + { + if(sectorSize == 0) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try { writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + basepath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); + mediaTags = new Dictionary<MediaTagType, byte[]>(); + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) + { + // Geometry is not stored in image + return true; + } + + public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + public bool SetDumpHardware(List<DumpHardwareType> dumpHardware) + { + // Not supported + return false; + } + + public bool SetCicmMetadata(CICMMetadataType metadata) + { + // Not supported + return false; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(!SupportedMediaTags.Contains(tag)) + { + ErrorMessage = $"Tried to write unsupported media tag {tag}."; + return false; + } + + if(mediaTags.ContainsKey(tag)) mediaTags.Remove(tag); + + mediaTags.Add(tag, data); + return true; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != imageInfo.SectorSize) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % imageInfo.SectorSize != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * imageInfo.SectorSize), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List<Track> tracks) + { + if(tracks.Count <= 1) return true; + + ErrorMessage = "This format supports only 1 track"; + return false; + } + + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + writingStream.Flush(); + writingStream.Close(); + IsWriting = false; + + foreach(KeyValuePair<MediaTagType, byte[]> tag in mediaTags) + { + string suffix = readWriteSidecars.Concat(writeOnlySidecars).Where(t => t.tag == tag.Key) + .Select(t => t.name).FirstOrDefault(); + + if(suffix == null) continue; + + FileStream tagStream = + new FileStream(basepath + suffix, FileMode.Create, FileAccess.ReadWrite, FileShare.None); + tagStream.Write(tag.Value, 0, tag.Value.Length); + tagStream.Close(); + } + + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ZZZRawImage/ZZZRawImage.cs b/DiscImageChef.DiscImages/ZZZRawImage/ZZZRawImage.cs new file mode 100644 index 000000000..0fea8a6ff --- /dev/null +++ b/DiscImageChef.DiscImages/ZZZRawImage/ZZZRawImage.cs @@ -0,0 +1,81 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : ZZZRawImage.cs +// Author(s) : Natalia Portillo <claunia@claunia.com> +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages raw image, that is, user data sector by sector copy. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see <http://www.gnu.org/licenses/>. +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2018 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.IO; +using DiscImageChef.CommonTypes.Enums; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; + +namespace DiscImageChef.DiscImages +{ + public partial class ZZZRawImage : IWritableImage + { + string basepath; + bool differentTrackZeroSize; + string extension; + bool hasSubchannel; + ImageInfo imageInfo; + Dictionary<MediaTagType, byte[]> mediaTags; + bool mode2; + bool rawCompactDisc; + IFilter rawImageFilter; + FileStream writingStream; + + public ZZZRawImage() + { + imageInfo = new ImageInfo + { + ReadableSectorTags = new List<SectorTagType>(), + ReadableMediaTags = new List<MediaTagType>(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = null, + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, + DriveFirmwareRevision = null + }; + } + } +} \ No newline at end of file