From 17bc763761eb1a78f69d00cf9ebacc09feccbe38 Mon Sep 17 00:00:00 2001 From: Gustave Monce Date: Sat, 14 Aug 2021 11:04:37 +0200 Subject: [PATCH] Project: Move WPinternals code to a WPinternals folder --- WPinternals.sln | 2 +- {7zip => WPinternals/7zip}/Common/CRC.cs | 128 +- .../7zip}/Common/CommandLineParser.cs | 650 +- {7zip => WPinternals/7zip}/Common/InBuffer.cs | 156 +- .../7zip}/Common/OutBuffer.cs | 104 +- .../7zip}/Compress/LZ/IMatchFinder.cs | 48 +- .../7zip}/Compress/LZ/LzBinTree.cs | 816 +- .../7zip}/Compress/LZ/LzInWindow.cs | 310 +- .../7zip}/Compress/LZ/LzOutWindow.cs | 274 +- .../7zip}/Compress/LZMA/LzmaBase.cs | 176 +- .../7zip}/Compress/LZMA/LzmaDecoder.cs | 930 +- .../7zip}/Compress/LZMA/LzmaEncoder.cs | 3384 ++--- .../7zip}/Compress/RangeCoder/RangeCoder.cs | 486 +- .../Compress/RangeCoder/RangeCoderBit.cs | 254 +- .../Compress/RangeCoder/RangeCoderBitTree.cs | 328 +- {7zip => WPinternals/7zip}/ICoder.cs | 346 +- App.xaml => WPinternals/App.xaml | 224 +- App.xaml.cs => WPinternals/App.xaml.cs | 206 +- CommandLine.cs => WPinternals/CommandLine.cs | 4218 +++--- .../CoreCompat/EncodingHelper.cs | 0 .../DiscUtils.Core/Internal/Utilities.cs | 0 .../DiscUtils}/DiscUtils.Fat/ClusterReader.cs | 0 .../DiscUtils}/DiscUtils.Fat/ClusterStream.cs | 0 .../DiscUtils.Fat/DirectoryEntry.cs | 0 .../DiscUtils}/DiscUtils.Fat/FatAttributes.cs | 0 .../DiscUtils}/DiscUtils.Fat/FatBuffer.cs | 0 .../DiscUtils}/DiscUtils.Fat/FatFileStream.cs | 0 .../DiscUtils.Fat/FatFileSystemOptions.cs | 0 .../DiscUtils}/DiscUtils.Fat/FatType.cs | 0 .../DiscUtils.Fat/FileAllocationTable.cs | 0 .../DiscUtils}/DiscUtils.Fat/FileName.cs | 0 .../DiscUtils.Fat/FileSystemFactory.cs | 0 .../FirstClusterChangedDelegate.cs | 0 .../DiscUtils.Fat/Modified/Directory.cs | 0 .../DiscUtils.Fat/Modified/FatFileSystem.cs | 0 .../FilePickerControl.xaml | 82 +- .../FilePickerControl.xaml.cs | 1050 +- .../FolderSelectDialog.cs | 240 +- .../HelperClasses.cs | 4704 +++---- Logo-Small.png => WPinternals/Logo-Small.png | Bin Logo.png => WPinternals/Logo.png | Bin .../Models}/ByteOperations.cs | 802 +- {Models => WPinternals/Models}/FFU.cs | 974 +- {Models => WPinternals/Models}/GPT.cs | 1424 +- {Models => WPinternals/Models}/LZMA.cs | 624 +- .../Models}/LumiaDownloadModel.cs | 1720 +-- {Models => WPinternals/Models}/MassStorage.cs | 1102 +- .../Models}/NativeMethods.cs | 604 +- .../Models}/NokiaFlashModel.cs | 2818 ++-- .../Models}/NokiaPhoneModel.cs | 824 +- {Models => WPinternals/Models}/PatchEngine.cs | 1218 +- {Models => WPinternals/Models}/Privileges.cs | 1274 +- .../Models}/QualcommDownload.cs | 290 +- .../Models}/QualcommFlasher.cs | 456 +- .../Models}/QualcommLoader.cs | 248 +- .../Models}/QualcommPartition.cs | 348 +- .../Models}/QualcommSahara.cs | 1200 +- .../Models}/QualcommSerial.cs | 758 +- {Models => WPinternals/Models}/SBL1.cs | 238 +- {Models => WPinternals/Models}/SBL2.cs | 112 +- {Models => WPinternals/Models}/SBL3.cs | 176 +- {Models => WPinternals/Models}/UEFI.cs | 1294 +- .../PatchDefinitions.xml | 10858 ++++++++-------- .../PhoneReboot.png | Bin .../Properties}/AssemblyInfo.cs | 102 +- .../PublishProfiles/FolderProfile.pubxml.user | 0 .../Properties}/Resources.Designer.cs | 126 +- .../Properties}/Resources.resx | 232 +- .../Properties}/Settings.Designer.cs | 52 +- .../Properties}/Settings.settings | 12 +- SB => WPinternals/SB | Bin SBA => WPinternals/SBA | Bin SBMSM => WPinternals/SBMSM | Bin Terminal.cs => WPinternals/Terminal.cs | 136 +- TestCode.cs => WPinternals/TestCode.cs | 590 +- .../ViewModels}/AboutViewModel.cs | 86 +- .../BackupTargetSelectionViewModel.cs | 474 +- .../ViewModels}/BackupViewModel.cs | 980 +- .../ViewModels}/BusyViewModel.cs | 434 +- .../ViewModels}/ContextViewModel.cs | 314 +- .../ViewModels}/DisclaimerAndNdaViewModel.cs | 120 +- .../ViewModels}/DisclaimerViewModel.cs | 122 +- .../ViewModels}/DownloadsViewModel.cs | 1930 +-- .../DumpRomTargetSelectionViewModel.cs | 456 +- .../ViewModels}/DumpRomViewModel.cs | 346 +- .../ViewModels}/GettingStartedViewModel.cs | 94 +- .../LumiaFlashRomSourceSelectionViewModel.cs | 558 +- .../ViewModels}/LumiaFlashRomViewModel.cs | 1546 +-- .../ViewModels}/LumiaInfoViewModel.cs | 176 +- .../ViewModels}/LumiaModeViewModel.cs | 264 +- .../ViewModels}/LumiaUnlockBootViewModel.cs | 2210 ++-- .../LumiaUnlockBootloaderViewModel.cs | 0 ...LumiaUnlockRootTargetSelectionViewModel.cs | 380 +- .../ViewModels}/LumiaUnlockRootViewModel.cs | 580 +- .../ViewModels}/LumiaV2UnlockBootViewModel.cs | 6008 ++++----- .../ViewModels}/LumiaV3FlashRomViewModel.cs | 0 .../ViewModels}/MainViewModel.cs | 970 +- .../ViewModels}/MessageViewModel.cs | 150 +- .../ViewModels}/NokiaBootloaderViewModel.cs | 0 .../ViewModels}/NokiaFlashViewModel.cs | 1524 +-- .../ViewModels}/NokiaLabelViewModel.cs | 594 +- .../ViewModels}/NokiaMassStorageViewModel.cs | 70 +- .../NokiaModeBootloaderViewModel.cs | 0 .../ViewModels}/NokiaModeFlashViewModel.cs | 240 +- .../ViewModels}/NokiaModeLabelViewModel.cs | 116 +- .../NokiaModeMassStorageViewModel.cs | 160 +- .../ViewModels}/NokiaModeNormalViewModel.cs | 110 +- .../ViewModels}/NokiaNormalViewModel.cs | 790 +- .../ViewModels}/NotImplementedViewModel.cs | 78 +- .../ViewModels}/PhoneNotifierViewModel.cs | 994 +- .../ViewModels}/RegistrationViewModel.cs | 286 +- .../RestoreSourceSelectionViewModel.cs | 408 +- .../ViewModels}/RestoreViewModel.cs | 408 +- .../ViewModels}/SwitchModeViewModel.cs | 1836 +-- {Views => WPinternals/Views}/About.xaml | 188 +- {Views => WPinternals/Views}/About.xaml.cs | 108 +- {Views => WPinternals/Views}/BackupView.xaml | 296 +- .../Views}/BackupView.xaml.cs | 110 +- .../Views}/BootRestoreResourcesView.xaml | 236 +- .../Views}/BootRestoreResourcesView.xaml.cs | 150 +- {Views => WPinternals/Views}/BusyView.xaml | 130 +- {Views => WPinternals/Views}/BusyView.xaml.cs | 78 +- {Views => WPinternals/Views}/ContextView.xaml | 96 +- .../Views}/ContextView.xaml.cs | 70 +- .../Views}/DisclaimerAndNdaView.xaml | 174 +- .../Views}/DisclaimerAndNdaView.xaml.cs | 70 +- .../Views}/DisclaimerView.xaml | 116 +- .../Views}/DisclaimerView.xaml.cs | 70 +- {Views => WPinternals/Views}/DumpRomView.xaml | 166 +- .../Views}/DumpRomView.xaml.cs | 162 +- {Views => WPinternals/Views}/Empty.xaml | 144 +- {Views => WPinternals/Views}/Empty.xaml.cs | 306 +- .../Views}/FlashResourcesView.xaml | 396 +- .../Views}/FlashResourcesView.xaml.cs | 130 +- .../Views}/FlashRomView.xaml | 388 +- .../Views}/FlashRomView.xaml.cs | 140 +- .../Views}/GettingStartedView.xaml | 1760 +-- .../Views}/GettingStartedView.xaml.cs | 180 +- .../Views}/LumiaDownloadView.xaml | 480 +- .../Views}/LumiaDownloadView.xaml.cs | 138 +- .../LumiaUndoRootTargetSelectionView.xaml | 146 +- .../LumiaUndoRootTargetSelectionView.xaml.cs | 100 +- .../LumiaUnlockRootTargetSelectionView.xaml | 238 +- ...LumiaUnlockRootTargetSelectionView.xaml.cs | 156 +- {Views => WPinternals/Views}/MainWindow.xaml | 472 +- .../Views}/MainWindow.xaml.cs | 164 +- {Views => WPinternals/Views}/MessageView.xaml | 96 +- .../Views}/MessageView.xaml.cs | 70 +- .../Views}/NokiaBootloaderView.xaml | 0 .../Views}/NokiaBootloaderView.xaml.cs | 0 .../Views}/NokiaFlashView.xaml | 680 +- .../Views}/NokiaFlashView.xaml.cs | 108 +- .../Views}/NokiaLabelView.xaml | 220 +- .../Views}/NokiaLabelView.xaml.cs | 94 +- .../Views}/NokiaMassStorageView.xaml | 132 +- .../Views}/NokiaMassStorageView.xaml.cs | 94 +- .../Views}/NokiaModeBootloaderView.xaml | 0 .../Views}/NokiaModeBootloaderView.xaml.cs | 0 .../Views}/NokiaModeFlashView.xaml | 184 +- .../Views}/NokiaModeFlashView.xaml.cs | 100 +- .../Views}/NokiaModeLabelView.xaml | 172 +- .../Views}/NokiaModeLabelView.xaml.cs | 100 +- .../Views}/NokiaModeMassStorageView.xaml | 238 +- .../Views}/NokiaModeMassStorageView.xaml.cs | 100 +- .../Views}/NokiaModeNormalView.xaml | 172 +- .../Views}/NokiaModeNormalView.xaml.cs | 100 +- .../Views}/NokiaNormalView.xaml | 330 +- .../Views}/NokiaNormalView.xaml.cs | 100 +- .../Views}/NotImplementedView.xaml | 66 +- .../Views}/NotImplementedView.xaml.cs | 70 +- .../Views}/RegistrationView.xaml | 142 +- .../Views}/RegistrationView.xaml.cs | 70 +- {Views => WPinternals/Views}/RestoreView.xaml | 160 +- .../Views}/RestoreView.xaml.cs | 124 +- .../Views}/StartupWindow.xaml | 60 +- .../Views}/StartupWindow.xaml.cs | 148 +- .../WPinternals.csproj | 0 .../WPinternals.csproj.user | 0 .../WPinternals.ico | Bin .../WPinternalsConfig.cs | 756 +- .../WinUSBNet}/API/APIException.cs | 96 +- .../WinUSBNet}/API/DeviceDetails.cs | 42 +- .../WinUSBNet}/API/DeviceManagement.cs | 740 +- .../WinUSBNet}/API/DeviceManagementAPI.cs | 360 +- .../WinUSBNet}/API/FileAPI.cs | 74 +- .../WinUSBNet}/API/WinUSBDevice.cs | 1058 +- .../WinUSBNet}/API/WinUSBDeviceAPI.cs | 398 +- .../WinUSBNet}/DeviceNotifyHook.cs | 388 +- {WinUSBNet => WPinternals/WinUSBNet}/USB.cs | 150 +- .../WinUSBNet}/USBAsyncResult.cs | 254 +- .../WinUSBNet}/USBDevice.cs | 1742 +-- .../WinUSBNet}/USBDeviceDescriptor.cs | 264 +- .../WinUSBNet}/USBDeviceInfo.cs | 182 +- .../WinUSBNet}/USBException.cs | 84 +- .../WinUSBNet}/USBInterface.cs | 288 +- .../WinUSBNet}/USBInterfaceCollection.cs | 326 +- .../WinUSBNet}/USBNotifier.cs | 480 +- .../WinUSBNet}/USBPipe.cs | 976 +- .../WinUSBNet}/USBPipeCollection.cs | 272 +- .../WinUSBNet}/USBPipePolicy.cs | 360 +- aerobusy.gif => WPinternals/aerobusy.gif | Bin app.config => WPinternals/app.config | 0 app.manifest => WPinternals/app.manifest | 22 +- 203 files changed, 48170 insertions(+), 48170 deletions(-) rename {7zip => WPinternals/7zip}/Common/CRC.cs (96%) rename {7zip => WPinternals/7zip}/Common/CommandLineParser.cs (97%) rename {7zip => WPinternals/7zip}/Common/InBuffer.cs (96%) rename {7zip => WPinternals/7zip}/Common/OutBuffer.cs (96%) rename {7zip => WPinternals/7zip}/Compress/LZ/IMatchFinder.cs (96%) rename {7zip => WPinternals/7zip}/Compress/LZ/LzBinTree.cs (97%) rename {7zip => WPinternals/7zip}/Compress/LZ/LzInWindow.cs (97%) rename {7zip => WPinternals/7zip}/Compress/LZ/LzOutWindow.cs (96%) rename {7zip => WPinternals/7zip}/Compress/LZMA/LzmaBase.cs (97%) rename {7zip => WPinternals/7zip}/Compress/LZMA/LzmaDecoder.cs (97%) rename {7zip => WPinternals/7zip}/Compress/LZMA/LzmaEncoder.cs (97%) rename {7zip => WPinternals/7zip}/Compress/RangeCoder/RangeCoder.cs (95%) rename {7zip => WPinternals/7zip}/Compress/RangeCoder/RangeCoderBit.cs (97%) rename {7zip => WPinternals/7zip}/Compress/RangeCoder/RangeCoderBitTree.cs (96%) rename {7zip => WPinternals/7zip}/ICoder.cs (96%) rename App.xaml => WPinternals/App.xaml (98%) rename App.xaml.cs => WPinternals/App.xaml.cs (97%) rename CommandLine.cs => WPinternals/CommandLine.cs (98%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Core/CoreCompat/EncodingHelper.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Core/Internal/Utilities.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/ClusterReader.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/ClusterStream.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/DirectoryEntry.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FatAttributes.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FatBuffer.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FatFileStream.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FatFileSystemOptions.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FatType.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FileAllocationTable.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FileName.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FileSystemFactory.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/FirstClusterChangedDelegate.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/Modified/Directory.cs (100%) rename {DiscUtils => WPinternals/DiscUtils}/DiscUtils.Fat/Modified/FatFileSystem.cs (100%) rename FilePickerControl.xaml => WPinternals/FilePickerControl.xaml (98%) rename FilePickerControl.xaml.cs => WPinternals/FilePickerControl.xaml.cs (96%) rename FolderSelectDialog.cs => WPinternals/FolderSelectDialog.cs (96%) rename HelperClasses.cs => WPinternals/HelperClasses.cs (97%) rename Logo-Small.png => WPinternals/Logo-Small.png (100%) rename Logo.png => WPinternals/Logo.png (100%) rename {Models => WPinternals/Models}/ByteOperations.cs (97%) rename {Models => WPinternals/Models}/FFU.cs (97%) rename {Models => WPinternals/Models}/GPT.cs (97%) rename {Models => WPinternals/Models}/LZMA.cs (97%) rename {Models => WPinternals/Models}/LumiaDownloadModel.cs (97%) rename {Models => WPinternals/Models}/MassStorage.cs (97%) rename {Models => WPinternals/Models}/NativeMethods.cs (97%) rename {Models => WPinternals/Models}/NokiaFlashModel.cs (97%) rename {Models => WPinternals/Models}/NokiaPhoneModel.cs (97%) rename {Models => WPinternals/Models}/PatchEngine.cs (97%) rename {Models => WPinternals/Models}/Privileges.cs (97%) rename {Models => WPinternals/Models}/QualcommDownload.cs (97%) rename {Models => WPinternals/Models}/QualcommFlasher.cs (97%) rename {Models => WPinternals/Models}/QualcommLoader.cs (97%) rename {Models => WPinternals/Models}/QualcommPartition.cs (97%) rename {Models => WPinternals/Models}/QualcommSahara.cs (97%) rename {Models => WPinternals/Models}/QualcommSerial.cs (97%) rename {Models => WPinternals/Models}/SBL1.cs (97%) rename {Models => WPinternals/Models}/SBL2.cs (97%) rename {Models => WPinternals/Models}/SBL3.cs (97%) rename {Models => WPinternals/Models}/UEFI.cs (97%) rename PatchDefinitions.xml => WPinternals/PatchDefinitions.xml (98%) rename PhoneReboot.png => WPinternals/PhoneReboot.png (100%) rename {Properties => WPinternals/Properties}/AssemblyInfo.cs (97%) rename {Properties => WPinternals/Properties}/PublishProfiles/FolderProfile.pubxml.user (100%) rename {Properties => WPinternals/Properties}/Resources.Designer.cs (97%) rename {Properties => WPinternals/Properties}/Resources.resx (97%) rename {Properties => WPinternals/Properties}/Settings.Designer.cs (97%) rename {Properties => WPinternals/Properties}/Settings.settings (97%) rename SB => WPinternals/SB (100%) rename SBA => WPinternals/SBA (100%) rename SBMSM => WPinternals/SBMSM (100%) rename Terminal.cs => WPinternals/Terminal.cs (97%) rename TestCode.cs => WPinternals/TestCode.cs (97%) rename {ViewModels => WPinternals/ViewModels}/AboutViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/BackupTargetSelectionViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/BackupViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/BusyViewModel.cs (96%) rename {ViewModels => WPinternals/ViewModels}/ContextViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/DisclaimerAndNdaViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/DisclaimerViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/DownloadsViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/DumpRomTargetSelectionViewModel.cs (96%) rename {ViewModels => WPinternals/ViewModels}/DumpRomViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/GettingStartedViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaFlashRomSourceSelectionViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaFlashRomViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaInfoViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaModeViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaUnlockBootViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaUnlockBootloaderViewModel.cs (100%) rename {ViewModels => WPinternals/ViewModels}/LumiaUnlockRootTargetSelectionViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaUnlockRootViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/LumiaV2UnlockBootViewModel.cs (98%) rename {ViewModels => WPinternals/ViewModels}/LumiaV3FlashRomViewModel.cs (100%) rename {ViewModels => WPinternals/ViewModels}/MainViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/MessageViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaBootloaderViewModel.cs (100%) rename {ViewModels => WPinternals/ViewModels}/NokiaFlashViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaLabelViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaMassStorageViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaModeBootloaderViewModel.cs (100%) rename {ViewModels => WPinternals/ViewModels}/NokiaModeFlashViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaModeLabelViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaModeMassStorageViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaModeNormalViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NokiaNormalViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/NotImplementedViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/PhoneNotifierViewModel.cs (98%) rename {ViewModels => WPinternals/ViewModels}/RegistrationViewModel.cs (96%) rename {ViewModels => WPinternals/ViewModels}/RestoreSourceSelectionViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/RestoreViewModel.cs (97%) rename {ViewModels => WPinternals/ViewModels}/SwitchModeViewModel.cs (98%) rename {Views => WPinternals/Views}/About.xaml (98%) rename {Views => WPinternals/Views}/About.xaml.cs (97%) rename {Views => WPinternals/Views}/BackupView.xaml (98%) rename {Views => WPinternals/Views}/BackupView.xaml.cs (97%) rename {Views => WPinternals/Views}/BootRestoreResourcesView.xaml (98%) rename {Views => WPinternals/Views}/BootRestoreResourcesView.xaml.cs (97%) rename {Views => WPinternals/Views}/BusyView.xaml (98%) rename {Views => WPinternals/Views}/BusyView.xaml.cs (97%) rename {Views => WPinternals/Views}/ContextView.xaml (98%) rename {Views => WPinternals/Views}/ContextView.xaml.cs (97%) rename {Views => WPinternals/Views}/DisclaimerAndNdaView.xaml (99%) rename {Views => WPinternals/Views}/DisclaimerAndNdaView.xaml.cs (97%) rename {Views => WPinternals/Views}/DisclaimerView.xaml (98%) rename {Views => WPinternals/Views}/DisclaimerView.xaml.cs (97%) rename {Views => WPinternals/Views}/DumpRomView.xaml (98%) rename {Views => WPinternals/Views}/DumpRomView.xaml.cs (97%) rename {Views => WPinternals/Views}/Empty.xaml (98%) rename {Views => WPinternals/Views}/Empty.xaml.cs (97%) rename {Views => WPinternals/Views}/FlashResourcesView.xaml (98%) rename {Views => WPinternals/Views}/FlashResourcesView.xaml.cs (97%) rename {Views => WPinternals/Views}/FlashRomView.xaml (98%) rename {Views => WPinternals/Views}/FlashRomView.xaml.cs (97%) rename {Views => WPinternals/Views}/GettingStartedView.xaml (98%) rename {Views => WPinternals/Views}/GettingStartedView.xaml.cs (97%) rename {Views => WPinternals/Views}/LumiaDownloadView.xaml (98%) rename {Views => WPinternals/Views}/LumiaDownloadView.xaml.cs (97%) rename {Views => WPinternals/Views}/LumiaUndoRootTargetSelectionView.xaml (98%) rename {Views => WPinternals/Views}/LumiaUndoRootTargetSelectionView.xaml.cs (97%) rename {Views => WPinternals/Views}/LumiaUnlockRootTargetSelectionView.xaml (98%) rename {Views => WPinternals/Views}/LumiaUnlockRootTargetSelectionView.xaml.cs (97%) rename {Views => WPinternals/Views}/MainWindow.xaml (98%) rename {Views => WPinternals/Views}/MainWindow.xaml.cs (97%) rename {Views => WPinternals/Views}/MessageView.xaml (98%) rename {Views => WPinternals/Views}/MessageView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaBootloaderView.xaml (100%) rename {Views => WPinternals/Views}/NokiaBootloaderView.xaml.cs (100%) rename {Views => WPinternals/Views}/NokiaFlashView.xaml (98%) rename {Views => WPinternals/Views}/NokiaFlashView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaLabelView.xaml (98%) rename {Views => WPinternals/Views}/NokiaLabelView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaMassStorageView.xaml (98%) rename {Views => WPinternals/Views}/NokiaMassStorageView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaModeBootloaderView.xaml (100%) rename {Views => WPinternals/Views}/NokiaModeBootloaderView.xaml.cs (100%) rename {Views => WPinternals/Views}/NokiaModeFlashView.xaml (98%) rename {Views => WPinternals/Views}/NokiaModeFlashView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaModeLabelView.xaml (98%) rename {Views => WPinternals/Views}/NokiaModeLabelView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaModeMassStorageView.xaml (98%) rename {Views => WPinternals/Views}/NokiaModeMassStorageView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaModeNormalView.xaml (98%) rename {Views => WPinternals/Views}/NokiaModeNormalView.xaml.cs (97%) rename {Views => WPinternals/Views}/NokiaNormalView.xaml (98%) rename {Views => WPinternals/Views}/NokiaNormalView.xaml.cs (97%) rename {Views => WPinternals/Views}/NotImplementedView.xaml (98%) rename {Views => WPinternals/Views}/NotImplementedView.xaml.cs (97%) rename {Views => WPinternals/Views}/RegistrationView.xaml (98%) rename {Views => WPinternals/Views}/RegistrationView.xaml.cs (97%) rename {Views => WPinternals/Views}/RestoreView.xaml (98%) rename {Views => WPinternals/Views}/RestoreView.xaml.cs (97%) rename {Views => WPinternals/Views}/StartupWindow.xaml (97%) rename {Views => WPinternals/Views}/StartupWindow.xaml.cs (97%) rename WPinternals.csproj => WPinternals/WPinternals.csproj (100%) rename WPinternals.csproj.user => WPinternals/WPinternals.csproj.user (100%) rename WPinternals.ico => WPinternals/WPinternals.ico (100%) rename WPinternalsConfig.cs => WPinternals/WPinternalsConfig.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/API/APIException.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/API/DeviceDetails.cs (95%) rename {WinUSBNet => WPinternals/WinUSBNet}/API/DeviceManagement.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/API/DeviceManagementAPI.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/API/FileAPI.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/API/WinUSBDevice.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/API/WinUSBDeviceAPI.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/DeviceNotifyHook.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/USB.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBAsyncResult.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBDevice.cs (98%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBDeviceDescriptor.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBDeviceInfo.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBException.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBInterface.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBInterfaceCollection.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBNotifier.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBPipe.cs (97%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBPipeCollection.cs (96%) rename {WinUSBNet => WPinternals/WinUSBNet}/USBPipePolicy.cs (97%) rename aerobusy.gif => WPinternals/aerobusy.gif (100%) rename app.config => WPinternals/app.config (100%) rename app.manifest => WPinternals/app.manifest (98%) diff --git a/WPinternals.sln b/WPinternals.sln index 7acdd45..d8e6f9d 100644 --- a/WPinternals.sln +++ b/WPinternals.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31606.5 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WPinternals", "WPinternals.csproj", "{AED6DEB8-F54C-4B41-9655-793E7096AE6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WPinternals", "WPinternals\WPinternals.csproj", "{AED6DEB8-F54C-4B41-9655-793E7096AE6E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/7zip/Common/CRC.cs b/WPinternals/7zip/Common/CRC.cs similarity index 96% rename from 7zip/Common/CRC.cs rename to WPinternals/7zip/Common/CRC.cs index ce5e76c..d9260cc 100644 --- a/7zip/Common/CRC.cs +++ b/WPinternals/7zip/Common/CRC.cs @@ -1,64 +1,64 @@ -// Common/CRC.cs - -namespace SevenZip -{ - internal class CRC - { - public static readonly uint[] Table; - - static CRC() - { - Table = new uint[256]; - const uint kPoly = 0xEDB88320; - for (uint i = 0; i < 256; i++) - { - uint r = i; - for (int j = 0; j < 8; j++) - { - if ((r & 1) != 0) - { - r = (r >> 1) ^ kPoly; - } - else - { - r >>= 1; - } - } - - Table[i] = r; - } - } - - private uint _value = 0xFFFFFFFF; - - public void Init() { _value = 0xFFFFFFFF; } - - public void UpdateByte(byte b) - { - _value = Table[((byte)_value) ^ b] ^ (_value >> 8); - } - - public void Update(byte[] data, uint offset, uint size) - { - for (uint i = 0; i < size; i++) - { - _value = Table[((byte)_value) ^ data[offset + i]] ^ (_value >> 8); - } - } - - public uint GetDigest() { return _value ^ 0xFFFFFFFF; } - - private static uint CalculateDigest(byte[] data, uint offset, uint size) - { - CRC crc = new(); - // crc.Init(); - crc.Update(data, offset, size); - return crc.GetDigest(); - } - - private static bool VerifyDigest(uint digest, byte[] data, uint offset, uint size) - { - return CalculateDigest(data, offset, size) == digest; - } - } -} +// Common/CRC.cs + +namespace SevenZip +{ + internal class CRC + { + public static readonly uint[] Table; + + static CRC() + { + Table = new uint[256]; + const uint kPoly = 0xEDB88320; + for (uint i = 0; i < 256; i++) + { + uint r = i; + for (int j = 0; j < 8; j++) + { + if ((r & 1) != 0) + { + r = (r >> 1) ^ kPoly; + } + else + { + r >>= 1; + } + } + + Table[i] = r; + } + } + + private uint _value = 0xFFFFFFFF; + + public void Init() { _value = 0xFFFFFFFF; } + + public void UpdateByte(byte b) + { + _value = Table[((byte)_value) ^ b] ^ (_value >> 8); + } + + public void Update(byte[] data, uint offset, uint size) + { + for (uint i = 0; i < size; i++) + { + _value = Table[((byte)_value) ^ data[offset + i]] ^ (_value >> 8); + } + } + + public uint GetDigest() { return _value ^ 0xFFFFFFFF; } + + private static uint CalculateDigest(byte[] data, uint offset, uint size) + { + CRC crc = new(); + // crc.Init(); + crc.Update(data, offset, size); + return crc.GetDigest(); + } + + private static bool VerifyDigest(uint digest, byte[] data, uint offset, uint size) + { + return CalculateDigest(data, offset, size) == digest; + } + } +} diff --git a/7zip/Common/CommandLineParser.cs b/WPinternals/7zip/Common/CommandLineParser.cs similarity index 97% rename from 7zip/Common/CommandLineParser.cs rename to WPinternals/7zip/Common/CommandLineParser.cs index 1419528..6facd78 100644 --- a/7zip/Common/CommandLineParser.cs +++ b/WPinternals/7zip/Common/CommandLineParser.cs @@ -1,325 +1,325 @@ -// CommandLineParser.cs - -using System; -using System.Collections; - -namespace SevenZip.CommandLineParser -{ - public enum SwitchType - { - Simple, - PostMinus, - LimitedPostString, - UnLimitedPostString, - PostChar - } - - public class SwitchForm - { - public string IDString; - public SwitchType Type; - public bool Multi; - public int MinLen; - public int MaxLen; - public string PostCharSet; - - public SwitchForm(string idString, SwitchType type, bool multi, - int minLen, int maxLen, string postCharSet) - { - IDString = idString; - Type = type; - Multi = multi; - MinLen = minLen; - MaxLen = maxLen; - PostCharSet = postCharSet; - } - public SwitchForm(string idString, SwitchType type, bool multi, int minLen) : - this(idString, type, multi, minLen, 0, "") - { - } - public SwitchForm(string idString, SwitchType type, bool multi) : - this(idString, type, multi, 0) - { - } - } - - public class SwitchResult - { - public bool ThereIs; - public bool WithMinus; - public ArrayList PostStrings = new(); - public int PostCharIndex; - public SwitchResult() - { - ThereIs = false; - } - } - - public class Parser - { - public ArrayList NonSwitchStrings = new(); - private readonly SwitchResult[] _switches; - - public Parser(int numSwitches) - { - _switches = new SwitchResult[numSwitches]; - for (int i = 0; i < numSwitches; i++) - { - _switches[i] = new SwitchResult(); - } - } - - private bool ParseString(string srcString, SwitchForm[] switchForms) - { - int len = srcString.Length; - if (len == 0) - { - return false; - } - - int pos = 0; - if (!IsItSwitchChar(srcString[pos])) - { - return false; - } - - while (pos < len) - { - if (IsItSwitchChar(srcString[pos])) - { - pos++; - } - - const int kNoLen = -1; - int matchedSwitchIndex = 0; - int maxLen = kNoLen; - for (int switchIndex = 0; switchIndex < _switches.Length; switchIndex++) - { - int switchLen = switchForms[switchIndex].IDString.Length; - if (switchLen <= maxLen || pos + switchLen > len) - { - continue; - } - - if (String.Compare(switchForms[switchIndex].IDString, 0, - srcString, pos, switchLen, true) == 0) - { - matchedSwitchIndex = switchIndex; - maxLen = switchLen; - } - } - if (maxLen == kNoLen) - { - throw new Exception("maxLen == kNoLen"); - } - - SwitchResult matchedSwitch = _switches[matchedSwitchIndex]; - SwitchForm switchForm = switchForms[matchedSwitchIndex]; - if ((!switchForm.Multi) && matchedSwitch.ThereIs) - { - throw new Exception("switch must be single"); - } - - matchedSwitch.ThereIs = true; - pos += maxLen; - int tailSize = len - pos; - SwitchType type = switchForm.Type; - switch (type) - { - case SwitchType.PostMinus: - { - if (tailSize == 0) - { - matchedSwitch.WithMinus = false; - } - else - { - matchedSwitch.WithMinus = srcString[pos] == kSwitchMinus; - if (matchedSwitch.WithMinus) - { - pos++; - } - } - break; - } - case SwitchType.PostChar: - { - if (tailSize < switchForm.MinLen) - { - throw new Exception("switch is not full"); - } - - string charSet = switchForm.PostCharSet; - const int kEmptyCharValue = -1; - if (tailSize == 0) - { - matchedSwitch.PostCharIndex = kEmptyCharValue; - } - else - { - int index = charSet.IndexOf(srcString[pos]); - if (index < 0) - { - matchedSwitch.PostCharIndex = kEmptyCharValue; - } - else - { - matchedSwitch.PostCharIndex = index; - pos++; - } - } - break; - } - case SwitchType.LimitedPostString: - case SwitchType.UnLimitedPostString: - { - int minLen = switchForm.MinLen; - if (tailSize < minLen) - { - throw new Exception("switch is not full"); - } - - if (type == SwitchType.UnLimitedPostString) - { - matchedSwitch.PostStrings.Add(srcString[pos..]); - return true; - } - String stringSwitch = srcString.Substring(pos, minLen); - pos += minLen; - for (int i = minLen; i < switchForm.MaxLen && pos < len; i++, pos++) - { - char c = srcString[pos]; - if (IsItSwitchChar(c)) - { - break; - } - - stringSwitch += c; - } - matchedSwitch.PostStrings.Add(stringSwitch); - break; - } - } - } - return true; - } - - public void ParseStrings(SwitchForm[] switchForms, string[] commandStrings) - { - int numCommandStrings = commandStrings.Length; - bool stopSwitch = false; - for (int i = 0; i < numCommandStrings; i++) - { - string s = commandStrings[i]; - if (stopSwitch) - { - NonSwitchStrings.Add(s); - } - else - if (s == kStopSwitchParsing) - { - stopSwitch = true; - } - else - if (!ParseString(s, switchForms)) - { - NonSwitchStrings.Add(s); - } - } - } - - public SwitchResult this[int index] { get { return _switches[index]; } } - - public static int ParseCommand(CommandForm[] commandForms, string commandString, - out string postString) - { - for (int i = 0; i < commandForms.Length; i++) - { - string id = commandForms[i].IDString; - if (commandForms[i].PostStringMode) - { - if (commandString.IndexOf(id) == 0) - { - postString = commandString[id.Length..]; - return i; - } - } - else - if (commandString == id) - { - postString = ""; - return i; - } - } - postString = ""; - return -1; - } - - private static bool ParseSubCharsCommand(int numForms, CommandSubCharsSet[] forms, - string commandString, ArrayList indices) - { - indices.Clear(); - int numUsedChars = 0; - for (int i = 0; i < numForms; i++) - { - CommandSubCharsSet charsSet = forms[i]; - int currentIndex = -1; - int len = charsSet.Chars.Length; - for (int j = 0; j < len; j++) - { - char c = charsSet.Chars[j]; - int newIndex = commandString.IndexOf(c); - if (newIndex >= 0) - { - if (currentIndex >= 0) - { - return false; - } - - if (commandString.IndexOf(c, newIndex + 1) >= 0) - { - return false; - } - - currentIndex = j; - numUsedChars++; - } - } - if (currentIndex == -1 && !charsSet.EmptyAllowed) - { - return false; - } - - indices.Add(currentIndex); - } - return numUsedChars == commandString.Length; - } - private const char kSwitchID1 = '-'; - private const char kSwitchID2 = '/'; - - private const char kSwitchMinus = '-'; - private const string kStopSwitchParsing = "--"; - - private static bool IsItSwitchChar(char c) - { - return c == kSwitchID1 || c == kSwitchID2; - } - } - - public class CommandForm - { - public string IDString = ""; - public bool PostStringMode = false; - public CommandForm(string idString, bool postStringMode) - { - IDString = idString; - PostStringMode = postStringMode; - } - } - - internal class CommandSubCharsSet - { - public string Chars = ""; - public bool EmptyAllowed = false; - } -} +// CommandLineParser.cs + +using System; +using System.Collections; + +namespace SevenZip.CommandLineParser +{ + public enum SwitchType + { + Simple, + PostMinus, + LimitedPostString, + UnLimitedPostString, + PostChar + } + + public class SwitchForm + { + public string IDString; + public SwitchType Type; + public bool Multi; + public int MinLen; + public int MaxLen; + public string PostCharSet; + + public SwitchForm(string idString, SwitchType type, bool multi, + int minLen, int maxLen, string postCharSet) + { + IDString = idString; + Type = type; + Multi = multi; + MinLen = minLen; + MaxLen = maxLen; + PostCharSet = postCharSet; + } + public SwitchForm(string idString, SwitchType type, bool multi, int minLen) : + this(idString, type, multi, minLen, 0, "") + { + } + public SwitchForm(string idString, SwitchType type, bool multi) : + this(idString, type, multi, 0) + { + } + } + + public class SwitchResult + { + public bool ThereIs; + public bool WithMinus; + public ArrayList PostStrings = new(); + public int PostCharIndex; + public SwitchResult() + { + ThereIs = false; + } + } + + public class Parser + { + public ArrayList NonSwitchStrings = new(); + private readonly SwitchResult[] _switches; + + public Parser(int numSwitches) + { + _switches = new SwitchResult[numSwitches]; + for (int i = 0; i < numSwitches; i++) + { + _switches[i] = new SwitchResult(); + } + } + + private bool ParseString(string srcString, SwitchForm[] switchForms) + { + int len = srcString.Length; + if (len == 0) + { + return false; + } + + int pos = 0; + if (!IsItSwitchChar(srcString[pos])) + { + return false; + } + + while (pos < len) + { + if (IsItSwitchChar(srcString[pos])) + { + pos++; + } + + const int kNoLen = -1; + int matchedSwitchIndex = 0; + int maxLen = kNoLen; + for (int switchIndex = 0; switchIndex < _switches.Length; switchIndex++) + { + int switchLen = switchForms[switchIndex].IDString.Length; + if (switchLen <= maxLen || pos + switchLen > len) + { + continue; + } + + if (String.Compare(switchForms[switchIndex].IDString, 0, + srcString, pos, switchLen, true) == 0) + { + matchedSwitchIndex = switchIndex; + maxLen = switchLen; + } + } + if (maxLen == kNoLen) + { + throw new Exception("maxLen == kNoLen"); + } + + SwitchResult matchedSwitch = _switches[matchedSwitchIndex]; + SwitchForm switchForm = switchForms[matchedSwitchIndex]; + if ((!switchForm.Multi) && matchedSwitch.ThereIs) + { + throw new Exception("switch must be single"); + } + + matchedSwitch.ThereIs = true; + pos += maxLen; + int tailSize = len - pos; + SwitchType type = switchForm.Type; + switch (type) + { + case SwitchType.PostMinus: + { + if (tailSize == 0) + { + matchedSwitch.WithMinus = false; + } + else + { + matchedSwitch.WithMinus = srcString[pos] == kSwitchMinus; + if (matchedSwitch.WithMinus) + { + pos++; + } + } + break; + } + case SwitchType.PostChar: + { + if (tailSize < switchForm.MinLen) + { + throw new Exception("switch is not full"); + } + + string charSet = switchForm.PostCharSet; + const int kEmptyCharValue = -1; + if (tailSize == 0) + { + matchedSwitch.PostCharIndex = kEmptyCharValue; + } + else + { + int index = charSet.IndexOf(srcString[pos]); + if (index < 0) + { + matchedSwitch.PostCharIndex = kEmptyCharValue; + } + else + { + matchedSwitch.PostCharIndex = index; + pos++; + } + } + break; + } + case SwitchType.LimitedPostString: + case SwitchType.UnLimitedPostString: + { + int minLen = switchForm.MinLen; + if (tailSize < minLen) + { + throw new Exception("switch is not full"); + } + + if (type == SwitchType.UnLimitedPostString) + { + matchedSwitch.PostStrings.Add(srcString[pos..]); + return true; + } + String stringSwitch = srcString.Substring(pos, minLen); + pos += minLen; + for (int i = minLen; i < switchForm.MaxLen && pos < len; i++, pos++) + { + char c = srcString[pos]; + if (IsItSwitchChar(c)) + { + break; + } + + stringSwitch += c; + } + matchedSwitch.PostStrings.Add(stringSwitch); + break; + } + } + } + return true; + } + + public void ParseStrings(SwitchForm[] switchForms, string[] commandStrings) + { + int numCommandStrings = commandStrings.Length; + bool stopSwitch = false; + for (int i = 0; i < numCommandStrings; i++) + { + string s = commandStrings[i]; + if (stopSwitch) + { + NonSwitchStrings.Add(s); + } + else + if (s == kStopSwitchParsing) + { + stopSwitch = true; + } + else + if (!ParseString(s, switchForms)) + { + NonSwitchStrings.Add(s); + } + } + } + + public SwitchResult this[int index] { get { return _switches[index]; } } + + public static int ParseCommand(CommandForm[] commandForms, string commandString, + out string postString) + { + for (int i = 0; i < commandForms.Length; i++) + { + string id = commandForms[i].IDString; + if (commandForms[i].PostStringMode) + { + if (commandString.IndexOf(id) == 0) + { + postString = commandString[id.Length..]; + return i; + } + } + else + if (commandString == id) + { + postString = ""; + return i; + } + } + postString = ""; + return -1; + } + + private static bool ParseSubCharsCommand(int numForms, CommandSubCharsSet[] forms, + string commandString, ArrayList indices) + { + indices.Clear(); + int numUsedChars = 0; + for (int i = 0; i < numForms; i++) + { + CommandSubCharsSet charsSet = forms[i]; + int currentIndex = -1; + int len = charsSet.Chars.Length; + for (int j = 0; j < len; j++) + { + char c = charsSet.Chars[j]; + int newIndex = commandString.IndexOf(c); + if (newIndex >= 0) + { + if (currentIndex >= 0) + { + return false; + } + + if (commandString.IndexOf(c, newIndex + 1) >= 0) + { + return false; + } + + currentIndex = j; + numUsedChars++; + } + } + if (currentIndex == -1 && !charsSet.EmptyAllowed) + { + return false; + } + + indices.Add(currentIndex); + } + return numUsedChars == commandString.Length; + } + private const char kSwitchID1 = '-'; + private const char kSwitchID2 = '/'; + + private const char kSwitchMinus = '-'; + private const string kStopSwitchParsing = "--"; + + private static bool IsItSwitchChar(char c) + { + return c == kSwitchID1 || c == kSwitchID2; + } + } + + public class CommandForm + { + public string IDString = ""; + public bool PostStringMode = false; + public CommandForm(string idString, bool postStringMode) + { + IDString = idString; + PostStringMode = postStringMode; + } + } + + internal class CommandSubCharsSet + { + public string Chars = ""; + public bool EmptyAllowed = false; + } +} diff --git a/7zip/Common/InBuffer.cs b/WPinternals/7zip/Common/InBuffer.cs similarity index 96% rename from 7zip/Common/InBuffer.cs rename to WPinternals/7zip/Common/InBuffer.cs index 6205287..36ff6b4 100644 --- a/7zip/Common/InBuffer.cs +++ b/WPinternals/7zip/Common/InBuffer.cs @@ -1,78 +1,78 @@ -// InBuffer.cs - -namespace SevenZip.Buffer -{ - public class InBuffer - { - private readonly byte[] m_Buffer; - private uint m_Pos; - private uint m_Limit; - private readonly uint m_BufferSize; - private System.IO.Stream m_Stream; - private bool m_StreamWasExhausted; - private ulong m_ProcessedSize; - - public InBuffer(uint bufferSize) - { - m_Buffer = new byte[bufferSize]; - m_BufferSize = bufferSize; - } - - public void Init(System.IO.Stream stream) - { - m_Stream = stream; - m_ProcessedSize = 0; - m_Limit = 0; - m_Pos = 0; - m_StreamWasExhausted = false; - } - - public bool ReadBlock() - { - if (m_StreamWasExhausted) - { - return false; - } - - m_ProcessedSize += m_Pos; - int aNumProcessedBytes = m_Stream.Read(m_Buffer, 0, (int)m_BufferSize); - m_Pos = 0; - m_Limit = (uint)aNumProcessedBytes; - m_StreamWasExhausted = aNumProcessedBytes == 0; - return !m_StreamWasExhausted; - } - - public void ReleaseStream() - { - // m_Stream.Close(); - m_Stream = null; - } - - public bool ReadByte(byte b) // check it - { - if (m_Pos >= m_Limit && !ReadBlock()) - { - return false; - } - - b = m_Buffer[m_Pos++]; - return true; - } - - public byte ReadByte() - { - // return (byte)m_Stream.ReadByte(); - if (m_Pos >= m_Limit && !ReadBlock()) - { - return 0xFF; - } - - return m_Buffer[m_Pos++]; - } - - public ulong GetProcessedSize() - { - return m_ProcessedSize + m_Pos; - } - } -} +// InBuffer.cs + +namespace SevenZip.Buffer +{ + public class InBuffer + { + private readonly byte[] m_Buffer; + private uint m_Pos; + private uint m_Limit; + private readonly uint m_BufferSize; + private System.IO.Stream m_Stream; + private bool m_StreamWasExhausted; + private ulong m_ProcessedSize; + + public InBuffer(uint bufferSize) + { + m_Buffer = new byte[bufferSize]; + m_BufferSize = bufferSize; + } + + public void Init(System.IO.Stream stream) + { + m_Stream = stream; + m_ProcessedSize = 0; + m_Limit = 0; + m_Pos = 0; + m_StreamWasExhausted = false; + } + + public bool ReadBlock() + { + if (m_StreamWasExhausted) + { + return false; + } + + m_ProcessedSize += m_Pos; + int aNumProcessedBytes = m_Stream.Read(m_Buffer, 0, (int)m_BufferSize); + m_Pos = 0; + m_Limit = (uint)aNumProcessedBytes; + m_StreamWasExhausted = aNumProcessedBytes == 0; + return !m_StreamWasExhausted; + } + + public void ReleaseStream() + { + // m_Stream.Close(); + m_Stream = null; + } + + public bool ReadByte(byte b) // check it + { + if (m_Pos >= m_Limit && !ReadBlock()) + { + return false; + } + + b = m_Buffer[m_Pos++]; + return true; + } + + public byte ReadByte() + { + // return (byte)m_Stream.ReadByte(); + if (m_Pos >= m_Limit && !ReadBlock()) + { + return 0xFF; + } + + return m_Buffer[m_Pos++]; + } + + public ulong GetProcessedSize() + { + return m_ProcessedSize + m_Pos; + } + } +} diff --git a/7zip/Common/OutBuffer.cs b/WPinternals/7zip/Common/OutBuffer.cs similarity index 96% rename from 7zip/Common/OutBuffer.cs rename to WPinternals/7zip/Common/OutBuffer.cs index a9657df..b371901 100644 --- a/7zip/Common/OutBuffer.cs +++ b/WPinternals/7zip/Common/OutBuffer.cs @@ -1,52 +1,52 @@ -// OutBuffer.cs - -namespace SevenZip.Buffer -{ - public class OutBuffer - { - private readonly byte[] m_Buffer; - private uint m_Pos; - private readonly uint m_BufferSize; - private System.IO.Stream m_Stream; - private ulong m_ProcessedSize; - - public OutBuffer(uint bufferSize) - { - m_Buffer = new byte[bufferSize]; - m_BufferSize = bufferSize; - } - - public void SetStream(System.IO.Stream stream) { m_Stream = stream; } - public void FlushStream() { m_Stream.Flush(); } - public void CloseStream() { m_Stream.Close(); } - public void ReleaseStream() { m_Stream = null; } - - public void Init() - { - m_ProcessedSize = 0; - m_Pos = 0; - } - - public void WriteByte(byte b) - { - m_Buffer[m_Pos++] = b; - if (m_Pos >= m_BufferSize) - { - FlushData(); - } - } - - public void FlushData() - { - if (m_Pos == 0) - { - return; - } - - m_Stream.Write(m_Buffer, 0, (int)m_Pos); - m_Pos = 0; - } - - public ulong GetProcessedSize() { return m_ProcessedSize + m_Pos; } - } -} +// OutBuffer.cs + +namespace SevenZip.Buffer +{ + public class OutBuffer + { + private readonly byte[] m_Buffer; + private uint m_Pos; + private readonly uint m_BufferSize; + private System.IO.Stream m_Stream; + private ulong m_ProcessedSize; + + public OutBuffer(uint bufferSize) + { + m_Buffer = new byte[bufferSize]; + m_BufferSize = bufferSize; + } + + public void SetStream(System.IO.Stream stream) { m_Stream = stream; } + public void FlushStream() { m_Stream.Flush(); } + public void CloseStream() { m_Stream.Close(); } + public void ReleaseStream() { m_Stream = null; } + + public void Init() + { + m_ProcessedSize = 0; + m_Pos = 0; + } + + public void WriteByte(byte b) + { + m_Buffer[m_Pos++] = b; + if (m_Pos >= m_BufferSize) + { + FlushData(); + } + } + + public void FlushData() + { + if (m_Pos == 0) + { + return; + } + + m_Stream.Write(m_Buffer, 0, (int)m_Pos); + m_Pos = 0; + } + + public ulong GetProcessedSize() { return m_ProcessedSize + m_Pos; } + } +} diff --git a/7zip/Compress/LZ/IMatchFinder.cs b/WPinternals/7zip/Compress/LZ/IMatchFinder.cs similarity index 96% rename from 7zip/Compress/LZ/IMatchFinder.cs rename to WPinternals/7zip/Compress/LZ/IMatchFinder.cs index e0ebbd8..ae4590b 100644 --- a/7zip/Compress/LZ/IMatchFinder.cs +++ b/WPinternals/7zip/Compress/LZ/IMatchFinder.cs @@ -1,24 +1,24 @@ -// IMatchFinder.cs - -using System; - -namespace SevenZip.Compression.LZ -{ - internal interface IInWindowStream - { - void SetStream(System.IO.Stream inStream); - void Init(); - void ReleaseStream(); - Byte GetIndexByte(Int32 index); - UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit); - UInt32 GetNumAvailableBytes(); - } - - internal interface IMatchFinder : IInWindowStream - { - void Create(UInt32 historySize, UInt32 keepAddBufferBefore, - UInt32 matchMaxLen, UInt32 keepAddBufferAfter); - UInt32 GetMatches(UInt32[] distances); - void Skip(UInt32 num); - } -} +// IMatchFinder.cs + +using System; + +namespace SevenZip.Compression.LZ +{ + internal interface IInWindowStream + { + void SetStream(System.IO.Stream inStream); + void Init(); + void ReleaseStream(); + Byte GetIndexByte(Int32 index); + UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit); + UInt32 GetNumAvailableBytes(); + } + + internal interface IMatchFinder : IInWindowStream + { + void Create(UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter); + UInt32 GetMatches(UInt32[] distances); + void Skip(UInt32 num); + } +} diff --git a/7zip/Compress/LZ/LzBinTree.cs b/WPinternals/7zip/Compress/LZ/LzBinTree.cs similarity index 97% rename from 7zip/Compress/LZ/LzBinTree.cs rename to WPinternals/7zip/Compress/LZ/LzBinTree.cs index c8df03c..5b02c4c 100644 --- a/7zip/Compress/LZ/LzBinTree.cs +++ b/WPinternals/7zip/Compress/LZ/LzBinTree.cs @@ -1,408 +1,408 @@ -// LzBinTree.cs - -using System; - -namespace SevenZip.Compression.LZ -{ - public class BinTree : InWindow, IMatchFinder - { - private UInt32 _cyclicBufferPos; - private UInt32 _cyclicBufferSize = 0; - private UInt32 _matchMaxLen; - - private UInt32[] _son; - private UInt32[] _hash; - - private UInt32 _cutValue = 0xFF; - private UInt32 _hashMask; - private UInt32 _hashSizeSum = 0; - - private bool HASH_ARRAY = true; - - private const UInt32 kHash2Size = 1 << 10; - private const UInt32 kHash3Size = 1 << 16; - private const UInt32 kBT2HashSize = 1 << 16; - private const UInt32 kStartMaxLen = 1; - private const UInt32 kHash3Offset = kHash2Size; - private const UInt32 kEmptyHashValue = 0; - private const UInt32 kMaxValForNormalize = ((UInt32)1 << 31) - 1; - - private UInt32 kNumHashDirectBytes = 0; - private UInt32 kMinMatchCheck = 4; - private UInt32 kFixHashSize = kHash2Size + kHash3Size; - - public void SetType(int numHashBytes) - { - HASH_ARRAY = numHashBytes > 2; - if (HASH_ARRAY) - { - kNumHashDirectBytes = 0; - kMinMatchCheck = 4; - kFixHashSize = kHash2Size + kHash3Size; - } - else - { - kNumHashDirectBytes = 2; - kMinMatchCheck = 2 + 1; - kFixHashSize = 0; - } - } - - public new void SetStream(System.IO.Stream stream) { base.SetStream(stream); } - public new void ReleaseStream() { base.ReleaseStream(); } - - public new void Init() - { - base.Init(); - for (UInt32 i = 0; i < _hashSizeSum; i++) - { - _hash[i] = kEmptyHashValue; - } - - _cyclicBufferPos = 0; - ReduceOffsets(-1); - } - - public new void MovePos() - { - if (++_cyclicBufferPos >= _cyclicBufferSize) - { - _cyclicBufferPos = 0; - } - - base.MovePos(); - if (_pos == kMaxValForNormalize) - { - Normalize(); - } - } - - public new Byte GetIndexByte(Int32 index) { return base.GetIndexByte(index); } - - public new UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) - { return base.GetMatchLen(index, distance, limit); } - - public new UInt32 GetNumAvailableBytes() { return base.GetNumAvailableBytes(); } - - public void Create(UInt32 historySize, UInt32 keepAddBufferBefore, - UInt32 matchMaxLen, UInt32 keepAddBufferAfter) - { - if (historySize > kMaxValForNormalize - 256) - { - throw new Exception(); - } - - _cutValue = 16 + (matchMaxLen >> 1); - - UInt32 windowReservSize = ((historySize + keepAddBufferBefore + - matchMaxLen + keepAddBufferAfter) / 2) + 256; - - Create(historySize + keepAddBufferBefore, matchMaxLen + keepAddBufferAfter, windowReservSize); - - _matchMaxLen = matchMaxLen; - - UInt32 cyclicBufferSize = historySize + 1; - if (_cyclicBufferSize != cyclicBufferSize) - { - _son = new UInt32[(_cyclicBufferSize = cyclicBufferSize) * 2]; - } - - UInt32 hs = kBT2HashSize; - - if (HASH_ARRAY) - { - hs = historySize - 1; - hs |= hs >> 1; - hs |= hs >> 2; - hs |= hs >> 4; - hs |= hs >> 8; - hs >>= 1; - hs |= 0xFFFF; - if (hs > (1 << 24)) - { - hs >>= 1; - } - - _hashMask = hs; - hs++; - hs += kFixHashSize; - } - if (hs != _hashSizeSum) - { - _hash = new UInt32[_hashSizeSum = hs]; - } - } - - public UInt32 GetMatches(UInt32[] distances) - { - UInt32 lenLimit; - if (_pos + _matchMaxLen <= _streamPos) - { - lenLimit = _matchMaxLen; - } - else - { - lenLimit = _streamPos - _pos; - if (lenLimit < kMinMatchCheck) - { - MovePos(); - return 0; - } - } - - UInt32 offset = 0; - UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; - UInt32 cur = _bufferOffset + _pos; - UInt32 maxLen = kStartMaxLen; // to avoid items for len < hashSize; - UInt32 hashValue, hash2Value = 0, hash3Value = 0; - - if (HASH_ARRAY) - { - UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1]; - hash2Value = temp & (kHash2Size - 1); - temp ^= (UInt32)_bufferBase[cur + 2] << 8; - hash3Value = temp & (kHash3Size - 1); - hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask; - } - else - { - hashValue = _bufferBase[cur] ^ ((UInt32)_bufferBase[cur + 1] << 8); - } - - UInt32 curMatch = _hash[kFixHashSize + hashValue]; - if (HASH_ARRAY) - { - UInt32 curMatch2 = _hash[hash2Value]; - UInt32 curMatch3 = _hash[kHash3Offset + hash3Value]; - _hash[hash2Value] = _pos; - _hash[kHash3Offset + hash3Value] = _pos; - if (curMatch2 > matchMinPos && _bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur]) - { - distances[offset++] = maxLen = 2; - distances[offset++] = _pos - curMatch2 - 1; - } - - if (curMatch3 > matchMinPos && _bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur]) - { - if (curMatch3 == curMatch2) - { - offset -= 2; - } - - distances[offset++] = maxLen = 3; - distances[offset++] = _pos - curMatch3 - 1; - curMatch2 = curMatch3; - } - - if (offset != 0 && curMatch2 == curMatch) - { - offset -= 2; - maxLen = kStartMaxLen; - } - } - - _hash[kFixHashSize + hashValue] = _pos; - - UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; - UInt32 ptr1 = _cyclicBufferPos << 1; - - UInt32 len0, len1; - len0 = len1 = kNumHashDirectBytes; - - if (kNumHashDirectBytes != 0 && curMatch > matchMinPos) - { - if (_bufferBase[_bufferOffset + curMatch + kNumHashDirectBytes] != - _bufferBase[cur + kNumHashDirectBytes]) - { - distances[offset++] = maxLen = kNumHashDirectBytes; - distances[offset++] = _pos - curMatch - 1; - } - } - - UInt32 count = _cutValue; - - while (true) - { - if (curMatch <= matchMinPos || count-- == 0) - { - _son[ptr0] = _son[ptr1] = kEmptyHashValue; - break; - } - UInt32 delta = _pos - curMatch; - UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ? - (_cyclicBufferPos - delta) : - (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; - - UInt32 pby1 = _bufferOffset + curMatch; - UInt32 len = Math.Min(len0, len1); - if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) - { - while (++len != lenLimit) - { - if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) - { - break; - } - } - - if (maxLen < len) - { - distances[offset++] = maxLen = len; - distances[offset++] = delta - 1; - if (len == lenLimit) - { - _son[ptr1] = _son[cyclicPos]; - _son[ptr0] = _son[cyclicPos + 1]; - break; - } - } - } - if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) - { - _son[ptr1] = curMatch; - ptr1 = cyclicPos + 1; - curMatch = _son[ptr1]; - len1 = len; - } - else - { - _son[ptr0] = curMatch; - ptr0 = cyclicPos; - curMatch = _son[ptr0]; - len0 = len; - } - } - MovePos(); - return offset; - } - - public void Skip(UInt32 num) - { - do - { - UInt32 lenLimit; - if (_pos + _matchMaxLen <= _streamPos) - { - lenLimit = _matchMaxLen; - } - else - { - lenLimit = _streamPos - _pos; - if (lenLimit < kMinMatchCheck) - { - MovePos(); - continue; - } - } - - UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; - UInt32 cur = _bufferOffset + _pos; - - UInt32 hashValue; - - if (HASH_ARRAY) - { - UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1]; - UInt32 hash2Value = temp & (kHash2Size - 1); - _hash[hash2Value] = _pos; - temp ^= (UInt32)_bufferBase[cur + 2] << 8; - UInt32 hash3Value = temp & (kHash3Size - 1); - _hash[kHash3Offset + hash3Value] = _pos; - hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask; - } - else - { - hashValue = _bufferBase[cur] ^ ((UInt32)_bufferBase[cur + 1] << 8); - } - - UInt32 curMatch = _hash[kFixHashSize + hashValue]; - _hash[kFixHashSize + hashValue] = _pos; - - UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; - UInt32 ptr1 = _cyclicBufferPos << 1; - - UInt32 len0, len1; - len0 = len1 = kNumHashDirectBytes; - - UInt32 count = _cutValue; - while (true) - { - if (curMatch <= matchMinPos || count-- == 0) - { - _son[ptr0] = _son[ptr1] = kEmptyHashValue; - break; - } - - UInt32 delta = _pos - curMatch; - UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ? - (_cyclicBufferPos - delta) : - (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; - - UInt32 pby1 = _bufferOffset + curMatch; - UInt32 len = Math.Min(len0, len1); - if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) - { - while (++len != lenLimit) - { - if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) - { - break; - } - } - - if (len == lenLimit) - { - _son[ptr1] = _son[cyclicPos]; - _son[ptr0] = _son[cyclicPos + 1]; - break; - } - } - if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) - { - _son[ptr1] = curMatch; - ptr1 = cyclicPos + 1; - curMatch = _son[ptr1]; - len1 = len; - } - else - { - _son[ptr0] = curMatch; - ptr0 = cyclicPos; - curMatch = _son[ptr0]; - len0 = len; - } - } - MovePos(); - } - while (--num != 0); - } - - private void NormalizeLinks(UInt32[] items, UInt32 numItems, UInt32 subValue) - { - for (UInt32 i = 0; i < numItems; i++) - { - UInt32 value = items[i]; - if (value <= subValue) - { - value = kEmptyHashValue; - } - else - { - value -= subValue; - } - - items[i] = value; - } - } - - private void Normalize() - { - UInt32 subValue = _pos - _cyclicBufferSize; - NormalizeLinks(_son, _cyclicBufferSize * 2, subValue); - NormalizeLinks(_hash, _hashSizeSum, subValue); - ReduceOffsets((Int32)subValue); - } - - public void SetCutValue(UInt32 cutValue) { _cutValue = cutValue; } - } -} +// LzBinTree.cs + +using System; + +namespace SevenZip.Compression.LZ +{ + public class BinTree : InWindow, IMatchFinder + { + private UInt32 _cyclicBufferPos; + private UInt32 _cyclicBufferSize = 0; + private UInt32 _matchMaxLen; + + private UInt32[] _son; + private UInt32[] _hash; + + private UInt32 _cutValue = 0xFF; + private UInt32 _hashMask; + private UInt32 _hashSizeSum = 0; + + private bool HASH_ARRAY = true; + + private const UInt32 kHash2Size = 1 << 10; + private const UInt32 kHash3Size = 1 << 16; + private const UInt32 kBT2HashSize = 1 << 16; + private const UInt32 kStartMaxLen = 1; + private const UInt32 kHash3Offset = kHash2Size; + private const UInt32 kEmptyHashValue = 0; + private const UInt32 kMaxValForNormalize = ((UInt32)1 << 31) - 1; + + private UInt32 kNumHashDirectBytes = 0; + private UInt32 kMinMatchCheck = 4; + private UInt32 kFixHashSize = kHash2Size + kHash3Size; + + public void SetType(int numHashBytes) + { + HASH_ARRAY = numHashBytes > 2; + if (HASH_ARRAY) + { + kNumHashDirectBytes = 0; + kMinMatchCheck = 4; + kFixHashSize = kHash2Size + kHash3Size; + } + else + { + kNumHashDirectBytes = 2; + kMinMatchCheck = 2 + 1; + kFixHashSize = 0; + } + } + + public new void SetStream(System.IO.Stream stream) { base.SetStream(stream); } + public new void ReleaseStream() { base.ReleaseStream(); } + + public new void Init() + { + base.Init(); + for (UInt32 i = 0; i < _hashSizeSum; i++) + { + _hash[i] = kEmptyHashValue; + } + + _cyclicBufferPos = 0; + ReduceOffsets(-1); + } + + public new void MovePos() + { + if (++_cyclicBufferPos >= _cyclicBufferSize) + { + _cyclicBufferPos = 0; + } + + base.MovePos(); + if (_pos == kMaxValForNormalize) + { + Normalize(); + } + } + + public new Byte GetIndexByte(Int32 index) { return base.GetIndexByte(index); } + + public new UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) + { return base.GetMatchLen(index, distance, limit); } + + public new UInt32 GetNumAvailableBytes() { return base.GetNumAvailableBytes(); } + + public void Create(UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter) + { + if (historySize > kMaxValForNormalize - 256) + { + throw new Exception(); + } + + _cutValue = 16 + (matchMaxLen >> 1); + + UInt32 windowReservSize = ((historySize + keepAddBufferBefore + + matchMaxLen + keepAddBufferAfter) / 2) + 256; + + Create(historySize + keepAddBufferBefore, matchMaxLen + keepAddBufferAfter, windowReservSize); + + _matchMaxLen = matchMaxLen; + + UInt32 cyclicBufferSize = historySize + 1; + if (_cyclicBufferSize != cyclicBufferSize) + { + _son = new UInt32[(_cyclicBufferSize = cyclicBufferSize) * 2]; + } + + UInt32 hs = kBT2HashSize; + + if (HASH_ARRAY) + { + hs = historySize - 1; + hs |= hs >> 1; + hs |= hs >> 2; + hs |= hs >> 4; + hs |= hs >> 8; + hs >>= 1; + hs |= 0xFFFF; + if (hs > (1 << 24)) + { + hs >>= 1; + } + + _hashMask = hs; + hs++; + hs += kFixHashSize; + } + if (hs != _hashSizeSum) + { + _hash = new UInt32[_hashSizeSum = hs]; + } + } + + public UInt32 GetMatches(UInt32[] distances) + { + UInt32 lenLimit; + if (_pos + _matchMaxLen <= _streamPos) + { + lenLimit = _matchMaxLen; + } + else + { + lenLimit = _streamPos - _pos; + if (lenLimit < kMinMatchCheck) + { + MovePos(); + return 0; + } + } + + UInt32 offset = 0; + UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; + UInt32 cur = _bufferOffset + _pos; + UInt32 maxLen = kStartMaxLen; // to avoid items for len < hashSize; + UInt32 hashValue, hash2Value = 0, hash3Value = 0; + + if (HASH_ARRAY) + { + UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1]; + hash2Value = temp & (kHash2Size - 1); + temp ^= (UInt32)_bufferBase[cur + 2] << 8; + hash3Value = temp & (kHash3Size - 1); + hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask; + } + else + { + hashValue = _bufferBase[cur] ^ ((UInt32)_bufferBase[cur + 1] << 8); + } + + UInt32 curMatch = _hash[kFixHashSize + hashValue]; + if (HASH_ARRAY) + { + UInt32 curMatch2 = _hash[hash2Value]; + UInt32 curMatch3 = _hash[kHash3Offset + hash3Value]; + _hash[hash2Value] = _pos; + _hash[kHash3Offset + hash3Value] = _pos; + if (curMatch2 > matchMinPos && _bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur]) + { + distances[offset++] = maxLen = 2; + distances[offset++] = _pos - curMatch2 - 1; + } + + if (curMatch3 > matchMinPos && _bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur]) + { + if (curMatch3 == curMatch2) + { + offset -= 2; + } + + distances[offset++] = maxLen = 3; + distances[offset++] = _pos - curMatch3 - 1; + curMatch2 = curMatch3; + } + + if (offset != 0 && curMatch2 == curMatch) + { + offset -= 2; + maxLen = kStartMaxLen; + } + } + + _hash[kFixHashSize + hashValue] = _pos; + + UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; + UInt32 ptr1 = _cyclicBufferPos << 1; + + UInt32 len0, len1; + len0 = len1 = kNumHashDirectBytes; + + if (kNumHashDirectBytes != 0 && curMatch > matchMinPos) + { + if (_bufferBase[_bufferOffset + curMatch + kNumHashDirectBytes] != + _bufferBase[cur + kNumHashDirectBytes]) + { + distances[offset++] = maxLen = kNumHashDirectBytes; + distances[offset++] = _pos - curMatch - 1; + } + } + + UInt32 count = _cutValue; + + while (true) + { + if (curMatch <= matchMinPos || count-- == 0) + { + _son[ptr0] = _son[ptr1] = kEmptyHashValue; + break; + } + UInt32 delta = _pos - curMatch; + UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ? + (_cyclicBufferPos - delta) : + (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; + + UInt32 pby1 = _bufferOffset + curMatch; + UInt32 len = Math.Min(len0, len1); + if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) + { + while (++len != lenLimit) + { + if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) + { + break; + } + } + + if (maxLen < len) + { + distances[offset++] = maxLen = len; + distances[offset++] = delta - 1; + if (len == lenLimit) + { + _son[ptr1] = _son[cyclicPos]; + _son[ptr0] = _son[cyclicPos + 1]; + break; + } + } + } + if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) + { + _son[ptr1] = curMatch; + ptr1 = cyclicPos + 1; + curMatch = _son[ptr1]; + len1 = len; + } + else + { + _son[ptr0] = curMatch; + ptr0 = cyclicPos; + curMatch = _son[ptr0]; + len0 = len; + } + } + MovePos(); + return offset; + } + + public void Skip(UInt32 num) + { + do + { + UInt32 lenLimit; + if (_pos + _matchMaxLen <= _streamPos) + { + lenLimit = _matchMaxLen; + } + else + { + lenLimit = _streamPos - _pos; + if (lenLimit < kMinMatchCheck) + { + MovePos(); + continue; + } + } + + UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; + UInt32 cur = _bufferOffset + _pos; + + UInt32 hashValue; + + if (HASH_ARRAY) + { + UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1]; + UInt32 hash2Value = temp & (kHash2Size - 1); + _hash[hash2Value] = _pos; + temp ^= (UInt32)_bufferBase[cur + 2] << 8; + UInt32 hash3Value = temp & (kHash3Size - 1); + _hash[kHash3Offset + hash3Value] = _pos; + hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask; + } + else + { + hashValue = _bufferBase[cur] ^ ((UInt32)_bufferBase[cur + 1] << 8); + } + + UInt32 curMatch = _hash[kFixHashSize + hashValue]; + _hash[kFixHashSize + hashValue] = _pos; + + UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; + UInt32 ptr1 = _cyclicBufferPos << 1; + + UInt32 len0, len1; + len0 = len1 = kNumHashDirectBytes; + + UInt32 count = _cutValue; + while (true) + { + if (curMatch <= matchMinPos || count-- == 0) + { + _son[ptr0] = _son[ptr1] = kEmptyHashValue; + break; + } + + UInt32 delta = _pos - curMatch; + UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ? + (_cyclicBufferPos - delta) : + (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; + + UInt32 pby1 = _bufferOffset + curMatch; + UInt32 len = Math.Min(len0, len1); + if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) + { + while (++len != lenLimit) + { + if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) + { + break; + } + } + + if (len == lenLimit) + { + _son[ptr1] = _son[cyclicPos]; + _son[ptr0] = _son[cyclicPos + 1]; + break; + } + } + if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) + { + _son[ptr1] = curMatch; + ptr1 = cyclicPos + 1; + curMatch = _son[ptr1]; + len1 = len; + } + else + { + _son[ptr0] = curMatch; + ptr0 = cyclicPos; + curMatch = _son[ptr0]; + len0 = len; + } + } + MovePos(); + } + while (--num != 0); + } + + private void NormalizeLinks(UInt32[] items, UInt32 numItems, UInt32 subValue) + { + for (UInt32 i = 0; i < numItems; i++) + { + UInt32 value = items[i]; + if (value <= subValue) + { + value = kEmptyHashValue; + } + else + { + value -= subValue; + } + + items[i] = value; + } + } + + private void Normalize() + { + UInt32 subValue = _pos - _cyclicBufferSize; + NormalizeLinks(_son, _cyclicBufferSize * 2, subValue); + NormalizeLinks(_hash, _hashSizeSum, subValue); + ReduceOffsets((Int32)subValue); + } + + public void SetCutValue(UInt32 cutValue) { _cutValue = cutValue; } + } +} diff --git a/7zip/Compress/LZ/LzInWindow.cs b/WPinternals/7zip/Compress/LZ/LzInWindow.cs similarity index 97% rename from 7zip/Compress/LZ/LzInWindow.cs rename to WPinternals/7zip/Compress/LZ/LzInWindow.cs index f54e368..f9f5603 100644 --- a/7zip/Compress/LZ/LzInWindow.cs +++ b/WPinternals/7zip/Compress/LZ/LzInWindow.cs @@ -1,155 +1,155 @@ -// LzInWindow.cs - -using System; - -namespace SevenZip.Compression.LZ -{ - public class InWindow - { - public Byte[] _bufferBase = null; // pointer to buffer with data - private System.IO.Stream _stream; - private UInt32 _posLimit; // offset (from _buffer) of first byte when new block reading must be done - private bool _streamEndWasReached; // if (true) then _streamPos shows real end of stream - - private UInt32 _pointerToLastSafePosition; - - public UInt32 _bufferOffset; - - public UInt32 _blockSize; // Size of Allocated memory block - public UInt32 _pos; // offset (from _buffer) of curent byte - private UInt32 _keepSizeBefore; // how many BYTEs must be kept in buffer before _pos - private UInt32 _keepSizeAfter; // how many BYTEs must be kept buffer after _pos - public UInt32 _streamPos; // offset (from _buffer) of first not read byte from Stream - - public void MoveBlock() - { - UInt32 offset = _bufferOffset + _pos - _keepSizeBefore; - // we need one additional byte, since MovePos moves on 1 byte. - if (offset > 0) - { - offset--; - } - - UInt32 numBytes = _bufferOffset + _streamPos - offset; - - // check negative offset ???? - for (UInt32 i = 0; i < numBytes; i++) - { - _bufferBase[i] = _bufferBase[offset + i]; - } - - _bufferOffset -= offset; - } - - public virtual void ReadBlock() - { - if (_streamEndWasReached) - { - return; - } - - while (true) - { - int size = (int)(0 - _bufferOffset + _blockSize - _streamPos); - if (size == 0) - { - return; - } - - int numReadBytes = _stream.Read(_bufferBase, (int)(_bufferOffset + _streamPos), size); - if (numReadBytes == 0) - { - _posLimit = _streamPos; - UInt32 pointerToPostion = _bufferOffset + _posLimit; - if (pointerToPostion > _pointerToLastSafePosition) - { - _posLimit = _pointerToLastSafePosition - _bufferOffset; - } - - _streamEndWasReached = true; - return; - } - _streamPos += (UInt32)numReadBytes; - if (_streamPos >= _pos + _keepSizeAfter) - { - _posLimit = _streamPos - _keepSizeAfter; - } - } - } - - private void Free() { _bufferBase = null; } - - public void Create(UInt32 keepSizeBefore, UInt32 keepSizeAfter, UInt32 keepSizeReserv) - { - _keepSizeBefore = keepSizeBefore; - _keepSizeAfter = keepSizeAfter; - UInt32 blockSize = keepSizeBefore + keepSizeAfter + keepSizeReserv; - if (_bufferBase == null || _blockSize != blockSize) - { - Free(); - _blockSize = blockSize; - _bufferBase = new Byte[_blockSize]; - } - _pointerToLastSafePosition = _blockSize - keepSizeAfter; - } - - public void SetStream(System.IO.Stream stream) { _stream = stream; } - public void ReleaseStream() { _stream = null; } - - public void Init() - { - _bufferOffset = 0; - _pos = 0; - _streamPos = 0; - _streamEndWasReached = false; - ReadBlock(); - } - - public void MovePos() - { - _pos++; - if (_pos > _posLimit) - { - UInt32 pointerToPostion = _bufferOffset + _pos; - if (pointerToPostion > _pointerToLastSafePosition) - { - MoveBlock(); - } - - ReadBlock(); - } - } - - public Byte GetIndexByte(Int32 index) { return _bufferBase[_bufferOffset + _pos + index]; } - - // index + limit have not to exceed _keepSizeAfter; - public UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) - { - if (_streamEndWasReached && _pos + index + limit > _streamPos) - { - limit = _streamPos - (UInt32)(_pos + index); - } - - distance++; - // Byte *pby = _buffer + (size_t)_pos + index; - UInt32 pby = _bufferOffset + _pos + (UInt32)index; - - UInt32 i; - for (i = 0; i < limit && _bufferBase[pby + i] == _bufferBase[pby + i - distance]; i++) - { - } - - return i; - } - - public UInt32 GetNumAvailableBytes() { return _streamPos - _pos; } - - public void ReduceOffsets(Int32 subValue) - { - _bufferOffset += (UInt32)subValue; - _posLimit -= (UInt32)subValue; - _pos -= (UInt32)subValue; - _streamPos -= (UInt32)subValue; - } - } -} +// LzInWindow.cs + +using System; + +namespace SevenZip.Compression.LZ +{ + public class InWindow + { + public Byte[] _bufferBase = null; // pointer to buffer with data + private System.IO.Stream _stream; + private UInt32 _posLimit; // offset (from _buffer) of first byte when new block reading must be done + private bool _streamEndWasReached; // if (true) then _streamPos shows real end of stream + + private UInt32 _pointerToLastSafePosition; + + public UInt32 _bufferOffset; + + public UInt32 _blockSize; // Size of Allocated memory block + public UInt32 _pos; // offset (from _buffer) of curent byte + private UInt32 _keepSizeBefore; // how many BYTEs must be kept in buffer before _pos + private UInt32 _keepSizeAfter; // how many BYTEs must be kept buffer after _pos + public UInt32 _streamPos; // offset (from _buffer) of first not read byte from Stream + + public void MoveBlock() + { + UInt32 offset = _bufferOffset + _pos - _keepSizeBefore; + // we need one additional byte, since MovePos moves on 1 byte. + if (offset > 0) + { + offset--; + } + + UInt32 numBytes = _bufferOffset + _streamPos - offset; + + // check negative offset ???? + for (UInt32 i = 0; i < numBytes; i++) + { + _bufferBase[i] = _bufferBase[offset + i]; + } + + _bufferOffset -= offset; + } + + public virtual void ReadBlock() + { + if (_streamEndWasReached) + { + return; + } + + while (true) + { + int size = (int)(0 - _bufferOffset + _blockSize - _streamPos); + if (size == 0) + { + return; + } + + int numReadBytes = _stream.Read(_bufferBase, (int)(_bufferOffset + _streamPos), size); + if (numReadBytes == 0) + { + _posLimit = _streamPos; + UInt32 pointerToPostion = _bufferOffset + _posLimit; + if (pointerToPostion > _pointerToLastSafePosition) + { + _posLimit = _pointerToLastSafePosition - _bufferOffset; + } + + _streamEndWasReached = true; + return; + } + _streamPos += (UInt32)numReadBytes; + if (_streamPos >= _pos + _keepSizeAfter) + { + _posLimit = _streamPos - _keepSizeAfter; + } + } + } + + private void Free() { _bufferBase = null; } + + public void Create(UInt32 keepSizeBefore, UInt32 keepSizeAfter, UInt32 keepSizeReserv) + { + _keepSizeBefore = keepSizeBefore; + _keepSizeAfter = keepSizeAfter; + UInt32 blockSize = keepSizeBefore + keepSizeAfter + keepSizeReserv; + if (_bufferBase == null || _blockSize != blockSize) + { + Free(); + _blockSize = blockSize; + _bufferBase = new Byte[_blockSize]; + } + _pointerToLastSafePosition = _blockSize - keepSizeAfter; + } + + public void SetStream(System.IO.Stream stream) { _stream = stream; } + public void ReleaseStream() { _stream = null; } + + public void Init() + { + _bufferOffset = 0; + _pos = 0; + _streamPos = 0; + _streamEndWasReached = false; + ReadBlock(); + } + + public void MovePos() + { + _pos++; + if (_pos > _posLimit) + { + UInt32 pointerToPostion = _bufferOffset + _pos; + if (pointerToPostion > _pointerToLastSafePosition) + { + MoveBlock(); + } + + ReadBlock(); + } + } + + public Byte GetIndexByte(Int32 index) { return _bufferBase[_bufferOffset + _pos + index]; } + + // index + limit have not to exceed _keepSizeAfter; + public UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) + { + if (_streamEndWasReached && _pos + index + limit > _streamPos) + { + limit = _streamPos - (UInt32)(_pos + index); + } + + distance++; + // Byte *pby = _buffer + (size_t)_pos + index; + UInt32 pby = _bufferOffset + _pos + (UInt32)index; + + UInt32 i; + for (i = 0; i < limit && _bufferBase[pby + i] == _bufferBase[pby + i - distance]; i++) + { + } + + return i; + } + + public UInt32 GetNumAvailableBytes() { return _streamPos - _pos; } + + public void ReduceOffsets(Int32 subValue) + { + _bufferOffset += (UInt32)subValue; + _posLimit -= (UInt32)subValue; + _pos -= (UInt32)subValue; + _streamPos -= (UInt32)subValue; + } + } +} diff --git a/7zip/Compress/LZ/LzOutWindow.cs b/WPinternals/7zip/Compress/LZ/LzOutWindow.cs similarity index 96% rename from 7zip/Compress/LZ/LzOutWindow.cs rename to WPinternals/7zip/Compress/LZ/LzOutWindow.cs index 24bbd49..1fa77fb 100644 --- a/7zip/Compress/LZ/LzOutWindow.cs +++ b/WPinternals/7zip/Compress/LZ/LzOutWindow.cs @@ -1,137 +1,137 @@ -// LzOutWindow.cs - -namespace SevenZip.Compression.LZ -{ - public class OutWindow - { - private byte[] _buffer = null; - private uint _pos; - private uint _windowSize = 0; - private uint _streamPos; - private System.IO.Stream _stream; - - public uint TrainSize = 0; - - public void Create(uint windowSize) - { - if (_windowSize != windowSize) - { - // System.GC.Collect(); - _buffer = new byte[windowSize]; - } - _windowSize = windowSize; - _pos = 0; - _streamPos = 0; - } - - public void Init(System.IO.Stream stream, bool solid) - { - ReleaseStream(); - _stream = stream; - if (!solid) - { - _streamPos = 0; - _pos = 0; - TrainSize = 0; - } - } - - public bool Train(System.IO.Stream stream) - { - long len = stream.Length; - uint size = (len < _windowSize) ? (uint)len : _windowSize; - TrainSize = size; - stream.Position = len - size; - _streamPos = _pos = 0; - while (size > 0) - { - uint curSize = _windowSize - _pos; - if (size < curSize) - { - curSize = size; - } - - int numReadBytes = stream.Read(_buffer, (int)_pos, (int)curSize); - if (numReadBytes == 0) - { - return false; - } - - size -= (uint)numReadBytes; - _pos += (uint)numReadBytes; - _streamPos += (uint)numReadBytes; - if (_pos == _windowSize) - { - _streamPos = _pos = 0; - } - } - return true; - } - - public void ReleaseStream() - { - Flush(); - _stream = null; - } - - public void Flush() - { - uint size = _pos - _streamPos; - if (size == 0) - { - return; - } - - _stream.Write(_buffer, (int)_streamPos, (int)size); - if (_pos >= _windowSize) - { - _pos = 0; - } - - _streamPos = _pos; - } - - public void CopyBlock(uint distance, uint len) - { - uint pos = _pos - distance - 1; - if (pos >= _windowSize) - { - pos += _windowSize; - } - - for (; len > 0; len--) - { - if (pos >= _windowSize) - { - pos = 0; - } - - _buffer[_pos++] = _buffer[pos++]; - if (_pos >= _windowSize) - { - Flush(); - } - } - } - - public void PutByte(byte b) - { - _buffer[_pos++] = b; - if (_pos >= _windowSize) - { - Flush(); - } - } - - public byte GetByte(uint distance) - { - uint pos = _pos - distance - 1; - if (pos >= _windowSize) - { - pos += _windowSize; - } - - return _buffer[pos]; - } - } -} +// LzOutWindow.cs + +namespace SevenZip.Compression.LZ +{ + public class OutWindow + { + private byte[] _buffer = null; + private uint _pos; + private uint _windowSize = 0; + private uint _streamPos; + private System.IO.Stream _stream; + + public uint TrainSize = 0; + + public void Create(uint windowSize) + { + if (_windowSize != windowSize) + { + // System.GC.Collect(); + _buffer = new byte[windowSize]; + } + _windowSize = windowSize; + _pos = 0; + _streamPos = 0; + } + + public void Init(System.IO.Stream stream, bool solid) + { + ReleaseStream(); + _stream = stream; + if (!solid) + { + _streamPos = 0; + _pos = 0; + TrainSize = 0; + } + } + + public bool Train(System.IO.Stream stream) + { + long len = stream.Length; + uint size = (len < _windowSize) ? (uint)len : _windowSize; + TrainSize = size; + stream.Position = len - size; + _streamPos = _pos = 0; + while (size > 0) + { + uint curSize = _windowSize - _pos; + if (size < curSize) + { + curSize = size; + } + + int numReadBytes = stream.Read(_buffer, (int)_pos, (int)curSize); + if (numReadBytes == 0) + { + return false; + } + + size -= (uint)numReadBytes; + _pos += (uint)numReadBytes; + _streamPos += (uint)numReadBytes; + if (_pos == _windowSize) + { + _streamPos = _pos = 0; + } + } + return true; + } + + public void ReleaseStream() + { + Flush(); + _stream = null; + } + + public void Flush() + { + uint size = _pos - _streamPos; + if (size == 0) + { + return; + } + + _stream.Write(_buffer, (int)_streamPos, (int)size); + if (_pos >= _windowSize) + { + _pos = 0; + } + + _streamPos = _pos; + } + + public void CopyBlock(uint distance, uint len) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + { + pos += _windowSize; + } + + for (; len > 0; len--) + { + if (pos >= _windowSize) + { + pos = 0; + } + + _buffer[_pos++] = _buffer[pos++]; + if (_pos >= _windowSize) + { + Flush(); + } + } + } + + public void PutByte(byte b) + { + _buffer[_pos++] = b; + if (_pos >= _windowSize) + { + Flush(); + } + } + + public byte GetByte(uint distance) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + { + pos += _windowSize; + } + + return _buffer[pos]; + } + } +} diff --git a/7zip/Compress/LZMA/LzmaBase.cs b/WPinternals/7zip/Compress/LZMA/LzmaBase.cs similarity index 97% rename from 7zip/Compress/LZMA/LzmaBase.cs rename to WPinternals/7zip/Compress/LZMA/LzmaBase.cs index 6b74137..265b016 100644 --- a/7zip/Compress/LZMA/LzmaBase.cs +++ b/WPinternals/7zip/Compress/LZMA/LzmaBase.cs @@ -1,88 +1,88 @@ -// LzmaBase.cs - -namespace SevenZip.Compression.LZMA -{ - internal abstract class Base - { - public const uint kNumRepDistances = 4; - public const uint kNumStates = 12; - - // static byte []kLiteralNextStates = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; - // static byte []kMatchNextStates = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; - // static byte []kRepNextStates = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; - // static byte []kShortRepNextStates = {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; - - public struct State - { - public uint Index; - public void Init() { Index = 0; } - public void UpdateChar() - { - if (Index < 4) - { - Index = 0; - } - else if (Index < 10) - { - Index -= 3; - } - else - { - Index -= 6; - } - } - public void UpdateMatch() { Index = (uint)(Index < 7 ? 7 : 10); } - public void UpdateRep() { Index = (uint)(Index < 7 ? 8 : 11); } - public void UpdateShortRep() { Index = (uint)(Index < 7 ? 9 : 11); } - public bool IsCharState() { return Index < 7; } - } - - public const int kNumPosSlotBits = 6; - public const int kDicLogSizeMin = 0; - // public const int kDicLogSizeMax = 30; - // public const uint kDistTableSizeMax = kDicLogSizeMax * 2; - - public const int kNumLenToPosStatesBits = 2; // it's for speed optimization - public const uint kNumLenToPosStates = 1 << kNumLenToPosStatesBits; - - public const uint kMatchMinLen = 2; - - public static uint GetLenToPosState(uint len) - { - len -= kMatchMinLen; - if (len < kNumLenToPosStates) - { - return len; - } - - return kNumLenToPosStates - 1; - } - - public const int kNumAlignBits = 4; - public const uint kAlignTableSize = 1 << kNumAlignBits; - public const uint kAlignMask = kAlignTableSize - 1; - - public const uint kStartPosModelIndex = 4; - public const uint kEndPosModelIndex = 14; - public const uint kNumPosModels = kEndPosModelIndex - kStartPosModelIndex; - - public const uint kNumFullDistances = 1 << ((int)kEndPosModelIndex / 2); - - public const uint kNumLitPosStatesBitsEncodingMax = 4; - public const uint kNumLitContextBitsMax = 8; - - public const int kNumPosStatesBitsMax = 4; - public const uint kNumPosStatesMax = 1 << kNumPosStatesBitsMax; - public const int kNumPosStatesBitsEncodingMax = 4; - public const uint kNumPosStatesEncodingMax = 1 << kNumPosStatesBitsEncodingMax; - - public const int kNumLowLenBits = 3; - public const int kNumMidLenBits = 3; - public const int kNumHighLenBits = 8; - public const uint kNumLowLenSymbols = 1 << kNumLowLenBits; - public const uint kNumMidLenSymbols = 1 << kNumMidLenBits; - public const uint kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols + - (1 << kNumHighLenBits); - public const uint kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1; - } -} +// LzmaBase.cs + +namespace SevenZip.Compression.LZMA +{ + internal abstract class Base + { + public const uint kNumRepDistances = 4; + public const uint kNumStates = 12; + + // static byte []kLiteralNextStates = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; + // static byte []kMatchNextStates = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; + // static byte []kRepNextStates = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; + // static byte []kShortRepNextStates = {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + + public struct State + { + public uint Index; + public void Init() { Index = 0; } + public void UpdateChar() + { + if (Index < 4) + { + Index = 0; + } + else if (Index < 10) + { + Index -= 3; + } + else + { + Index -= 6; + } + } + public void UpdateMatch() { Index = (uint)(Index < 7 ? 7 : 10); } + public void UpdateRep() { Index = (uint)(Index < 7 ? 8 : 11); } + public void UpdateShortRep() { Index = (uint)(Index < 7 ? 9 : 11); } + public bool IsCharState() { return Index < 7; } + } + + public const int kNumPosSlotBits = 6; + public const int kDicLogSizeMin = 0; + // public const int kDicLogSizeMax = 30; + // public const uint kDistTableSizeMax = kDicLogSizeMax * 2; + + public const int kNumLenToPosStatesBits = 2; // it's for speed optimization + public const uint kNumLenToPosStates = 1 << kNumLenToPosStatesBits; + + public const uint kMatchMinLen = 2; + + public static uint GetLenToPosState(uint len) + { + len -= kMatchMinLen; + if (len < kNumLenToPosStates) + { + return len; + } + + return kNumLenToPosStates - 1; + } + + public const int kNumAlignBits = 4; + public const uint kAlignTableSize = 1 << kNumAlignBits; + public const uint kAlignMask = kAlignTableSize - 1; + + public const uint kStartPosModelIndex = 4; + public const uint kEndPosModelIndex = 14; + public const uint kNumPosModels = kEndPosModelIndex - kStartPosModelIndex; + + public const uint kNumFullDistances = 1 << ((int)kEndPosModelIndex / 2); + + public const uint kNumLitPosStatesBitsEncodingMax = 4; + public const uint kNumLitContextBitsMax = 8; + + public const int kNumPosStatesBitsMax = 4; + public const uint kNumPosStatesMax = 1 << kNumPosStatesBitsMax; + public const int kNumPosStatesBitsEncodingMax = 4; + public const uint kNumPosStatesEncodingMax = 1 << kNumPosStatesBitsEncodingMax; + + public const int kNumLowLenBits = 3; + public const int kNumMidLenBits = 3; + public const int kNumHighLenBits = 8; + public const uint kNumLowLenSymbols = 1 << kNumLowLenBits; + public const uint kNumMidLenSymbols = 1 << kNumMidLenBits; + public const uint kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols + + (1 << kNumHighLenBits); + public const uint kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1; + } +} diff --git a/7zip/Compress/LZMA/LzmaDecoder.cs b/WPinternals/7zip/Compress/LZMA/LzmaDecoder.cs similarity index 97% rename from 7zip/Compress/LZMA/LzmaDecoder.cs rename to WPinternals/7zip/Compress/LZMA/LzmaDecoder.cs index a2b0847..52efad9 100644 --- a/7zip/Compress/LZMA/LzmaDecoder.cs +++ b/WPinternals/7zip/Compress/LZMA/LzmaDecoder.cs @@ -1,465 +1,465 @@ -// LzmaDecoder.cs - -using System; - -namespace SevenZip.Compression.LZMA -{ - using RangeCoder; - using System.Threading; - - public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream - { - private class LenDecoder - { - private BitDecoder m_Choice = new(); - private BitDecoder m_Choice2 = new(); - private readonly BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; - private readonly BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; - private readonly BitTreeDecoder m_HighCoder = new(Base.kNumHighLenBits); - private uint m_NumPosStates = 0; - - public void Create(uint numPosStates) - { - for (uint posState = m_NumPosStates; posState < numPosStates; posState++) - { - m_LowCoder[posState] = new BitTreeDecoder(Base.kNumLowLenBits); - m_MidCoder[posState] = new BitTreeDecoder(Base.kNumMidLenBits); - } - m_NumPosStates = numPosStates; - } - - public void Init() - { - m_Choice.Init(); - for (uint posState = 0; posState < m_NumPosStates; posState++) - { - m_LowCoder[posState].Init(); - m_MidCoder[posState].Init(); - } - m_Choice2.Init(); - m_HighCoder.Init(); - } - - public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState) - { - if (m_Choice.Decode(rangeDecoder) == 0) - { - return m_LowCoder[posState].Decode(rangeDecoder); - } - else - { - uint symbol = Base.kNumLowLenSymbols; - if (m_Choice2.Decode(rangeDecoder) == 0) - { - symbol += m_MidCoder[posState].Decode(rangeDecoder); - } - else - { - symbol += Base.kNumMidLenSymbols; - symbol += m_HighCoder.Decode(rangeDecoder); - } - return symbol; - } - } - } - - private class LiteralDecoder - { - private struct Decoder2 - { - private BitDecoder[] m_Decoders; - public void Create() { m_Decoders = new BitDecoder[0x300]; } - public void Init() { for (int i = 0; i < 0x300; i++) - { - m_Decoders[i].Init(); - } - } - - public byte DecodeNormal(RangeCoder.Decoder rangeDecoder) - { - uint symbol = 1; - do - { - symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); - } - while (symbol < 0x100); - return (byte)symbol; - } - - public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, byte matchByte) - { - uint symbol = 1; - do - { - uint matchBit = (uint)(matchByte >> 7) & 1; - matchByte <<= 1; - uint bit = m_Decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder); - symbol = (symbol << 1) | bit; - if (matchBit != bit) - { - while (symbol < 0x100) - { - symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); - } - - break; - } - } - while (symbol < 0x100); - return (byte)symbol; - } - } - - private Decoder2[] m_Coders; - private int m_NumPrevBits; - private int m_NumPosBits; - private uint m_PosMask; - - public void Create(int numPosBits, int numPrevBits) - { - if (m_Coders != null && m_NumPrevBits == numPrevBits && - m_NumPosBits == numPosBits) - { - return; - } - - m_NumPosBits = numPosBits; - m_PosMask = ((uint)1 << numPosBits) - 1; - m_NumPrevBits = numPrevBits; - uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); - m_Coders = new Decoder2[numStates]; - for (uint i = 0; i < numStates; i++) - { - m_Coders[i].Create(); - } - } - - public void Init() - { - uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); - for (uint i = 0; i < numStates; i++) - { - m_Coders[i].Init(); - } - } - - private uint GetState(uint pos, byte prevByte) - { return ((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits)); } - - public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) - { return m_Coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); } - - public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) - { return m_Coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); } - }; - - private readonly LZ.OutWindow m_OutWindow = new(); - private readonly RangeCoder.Decoder m_RangeDecoder = new(); - - private readonly BitDecoder[] m_IsMatchDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; - private readonly BitDecoder[] m_IsRepDecoders = new BitDecoder[Base.kNumStates]; - private readonly BitDecoder[] m_IsRepG0Decoders = new BitDecoder[Base.kNumStates]; - private readonly BitDecoder[] m_IsRepG1Decoders = new BitDecoder[Base.kNumStates]; - private readonly BitDecoder[] m_IsRepG2Decoders = new BitDecoder[Base.kNumStates]; - private readonly BitDecoder[] m_IsRep0LongDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; - - private readonly BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[Base.kNumLenToPosStates]; - private readonly BitDecoder[] m_PosDecoders = new BitDecoder[Base.kNumFullDistances - Base.kEndPosModelIndex]; - - private readonly BitTreeDecoder m_PosAlignDecoder = new(Base.kNumAlignBits); - - private readonly LenDecoder m_LenDecoder = new(); - private readonly LenDecoder m_RepLenDecoder = new(); - - private readonly LiteralDecoder m_LiteralDecoder = new(); - - private uint m_DictionarySize; - private uint m_DictionarySizeCheck; - - private uint m_PosStateMask; - - public Decoder() - { - m_DictionarySize = 0xFFFFFFFF; - for (int i = 0; i < Base.kNumLenToPosStates; i++) - { - m_PosSlotDecoder[i] = new BitTreeDecoder(Base.kNumPosSlotBits); - } - } - - private void SetDictionarySize(uint dictionarySize) - { - if (m_DictionarySize != dictionarySize) - { - m_DictionarySize = dictionarySize; - m_DictionarySizeCheck = Math.Max(m_DictionarySize, 1); - uint blockSize = Math.Max(m_DictionarySizeCheck, 1 << 12); - m_OutWindow.Create(blockSize); - } - } - - private void SetLiteralProperties(int lp, int lc) - { - if (lp > 8) - { - throw new InvalidParamException(); - } - - if (lc > 8) - { - throw new InvalidParamException(); - } - - m_LiteralDecoder.Create(lp, lc); - } - - private void SetPosBitsProperties(int pb) - { - if (pb > Base.kNumPosStatesBitsMax) - { - throw new InvalidParamException(); - } - - uint numPosStates = (uint)1 << pb; - m_LenDecoder.Create(numPosStates); - m_RepLenDecoder.Create(numPosStates); - m_PosStateMask = numPosStates - 1; - } - - private bool _solid = false; - private void Init(System.IO.Stream inStream, System.IO.Stream outStream) - { - m_RangeDecoder.Init(inStream); - m_OutWindow.Init(outStream, _solid); - - uint i; - for (i = 0; i < Base.kNumStates; i++) - { - for (uint j = 0; j <= m_PosStateMask; j++) - { - uint index = (i << Base.kNumPosStatesBitsMax) + j; - m_IsMatchDecoders[index].Init(); - m_IsRep0LongDecoders[index].Init(); - } - m_IsRepDecoders[i].Init(); - m_IsRepG0Decoders[i].Init(); - m_IsRepG1Decoders[i].Init(); - m_IsRepG2Decoders[i].Init(); - } - - m_LiteralDecoder.Init(); - for (i = 0; i < Base.kNumLenToPosStates; i++) - { - m_PosSlotDecoder[i].Init(); - } - // m_PosSpecDecoder.Init(); - for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++) - { - m_PosDecoders[i].Init(); - } - - m_LenDecoder.Init(); - m_RepLenDecoder.Init(); - m_PosAlignDecoder.Init(); - } - - public void Code(System.IO.Stream inStream, System.IO.Stream outStream, - Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken? token = null) - { - Init(inStream, outStream); - - Base.State state = new(); - state.Init(); - uint rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0; - - UInt64 nowPos64 = 0; - UInt64 outSize64 = (UInt64)outSize; - if (nowPos64 < outSize64) - { - if (m_IsMatchDecoders[state.Index << Base.kNumPosStatesBitsMax].Decode(m_RangeDecoder) != 0) - { - throw new DataErrorException(); - } - - state.UpdateChar(); - byte b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, 0, 0); - m_OutWindow.PutByte(b); - nowPos64++; - } - - try - { - while (nowPos64 < outSize64) - { - token?.ThrowIfCancellationRequested(); - - // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64); - // while(nowPos64 < next) - { - uint posState = (uint)nowPos64 & m_PosStateMask; - if (m_IsMatchDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) - { - byte b; - byte prevByte = m_OutWindow.GetByte(0); - b = !state.IsCharState() - ? m_LiteralDecoder.DecodeWithMatchByte(m_RangeDecoder, - (uint)nowPos64, prevByte, m_OutWindow.GetByte(rep0)) - : m_LiteralDecoder.DecodeNormal(m_RangeDecoder, (uint)nowPos64, prevByte); - - m_OutWindow.PutByte(b); - state.UpdateChar(); - nowPos64++; - } - else - { - uint len; - if (m_IsRepDecoders[state.Index].Decode(m_RangeDecoder) == 1) - { - if (m_IsRepG0Decoders[state.Index].Decode(m_RangeDecoder) == 0) - { - if (m_IsRep0LongDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) - { - state.UpdateShortRep(); - m_OutWindow.PutByte(m_OutWindow.GetByte(rep0)); - nowPos64++; - continue; - } - } - else - { - UInt32 distance; - if (m_IsRepG1Decoders[state.Index].Decode(m_RangeDecoder) == 0) - { - distance = rep1; - } - else - { - if (m_IsRepG2Decoders[state.Index].Decode(m_RangeDecoder) == 0) - { - distance = rep2; - } - else - { - distance = rep3; - rep3 = rep2; - } - rep2 = rep1; - } - rep1 = rep0; - rep0 = distance; - } - len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + Base.kMatchMinLen; - state.UpdateRep(); - } - else - { - rep3 = rep2; - rep2 = rep1; - rep1 = rep0; - len = Base.kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState); - state.UpdateMatch(); - uint posSlot = m_PosSlotDecoder[Base.GetLenToPosState(len)].Decode(m_RangeDecoder); - if (posSlot >= Base.kStartPosModelIndex) - { - int numDirectBits = (int)((posSlot >> 1) - 1); - rep0 = (2 | (posSlot & 1)) << numDirectBits; - if (posSlot < Base.kEndPosModelIndex) - { - rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders, - rep0 - posSlot - 1, m_RangeDecoder, numDirectBits); - } - else - { - rep0 += m_RangeDecoder.DecodeDirectBits( - numDirectBits - Base.kNumAlignBits) << Base.kNumAlignBits; - rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder); - } - } - else - { - rep0 = posSlot; - } - } - if (rep0 >= m_OutWindow.TrainSize + nowPos64 || rep0 >= m_DictionarySizeCheck) - { - if (rep0 == 0xFFFFFFFF) - { - break; - } - - throw new DataErrorException(); - } - m_OutWindow.CopyBlock(rep0, len); - nowPos64 += len; - } - } - } - } - catch (OperationCanceledException) - { - } - - m_OutWindow.Flush(); - m_OutWindow.ReleaseStream(); - m_RangeDecoder.ReleaseStream(); - } - - public void SetDecoderProperties(byte[] properties) - { - if (properties.Length < 5) - { - throw new InvalidParamException(); - } - - int lc = properties[0] % 9; - int remainder = properties[0] / 9; - int lp = remainder % 5; - int pb = remainder / 5; - if (pb > Base.kNumPosStatesBitsMax) - { - throw new InvalidParamException(); - } - - UInt32 dictionarySize = 0; - for (int i = 0; i < 4; i++) - { - dictionarySize += ((UInt32)properties[1 + i]) << (i * 8); - } - - SetDictionarySize(dictionarySize); - SetLiteralProperties(lp, lc); - SetPosBitsProperties(pb); - } - - public bool Train(System.IO.Stream stream) - { - _solid = true; - return m_OutWindow.Train(stream); - } - - /* - public override bool CanRead { get { return true; }} - public override bool CanWrite { get { return true; }} - public override bool CanSeek { get { return true; }} - public override long Length { get { return 0; }} - public override long Position - { - get { return 0; } - set { } - } - public override void Flush() { } - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } - public override void Write(byte[] buffer, int offset, int count) - { - } - public override long Seek(long offset, System.IO.SeekOrigin origin) - { - return 0; - } - public override void SetLength(long value) {} - */ - } -} +// LzmaDecoder.cs + +using System; + +namespace SevenZip.Compression.LZMA +{ + using RangeCoder; + using System.Threading; + + public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream + { + private class LenDecoder + { + private BitDecoder m_Choice = new(); + private BitDecoder m_Choice2 = new(); + private readonly BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + private readonly BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + private readonly BitTreeDecoder m_HighCoder = new(Base.kNumHighLenBits); + private uint m_NumPosStates = 0; + + public void Create(uint numPosStates) + { + for (uint posState = m_NumPosStates; posState < numPosStates; posState++) + { + m_LowCoder[posState] = new BitTreeDecoder(Base.kNumLowLenBits); + m_MidCoder[posState] = new BitTreeDecoder(Base.kNumMidLenBits); + } + m_NumPosStates = numPosStates; + } + + public void Init() + { + m_Choice.Init(); + for (uint posState = 0; posState < m_NumPosStates; posState++) + { + m_LowCoder[posState].Init(); + m_MidCoder[posState].Init(); + } + m_Choice2.Init(); + m_HighCoder.Init(); + } + + public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState) + { + if (m_Choice.Decode(rangeDecoder) == 0) + { + return m_LowCoder[posState].Decode(rangeDecoder); + } + else + { + uint symbol = Base.kNumLowLenSymbols; + if (m_Choice2.Decode(rangeDecoder) == 0) + { + symbol += m_MidCoder[posState].Decode(rangeDecoder); + } + else + { + symbol += Base.kNumMidLenSymbols; + symbol += m_HighCoder.Decode(rangeDecoder); + } + return symbol; + } + } + } + + private class LiteralDecoder + { + private struct Decoder2 + { + private BitDecoder[] m_Decoders; + public void Create() { m_Decoders = new BitDecoder[0x300]; } + public void Init() { for (int i = 0; i < 0x300; i++) + { + m_Decoders[i].Init(); + } + } + + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder) + { + uint symbol = 1; + do + { + symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); + } + while (symbol < 0x100); + return (byte)symbol; + } + + public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, byte matchByte) + { + uint symbol = 1; + do + { + uint matchBit = (uint)(matchByte >> 7) & 1; + matchByte <<= 1; + uint bit = m_Decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder); + symbol = (symbol << 1) | bit; + if (matchBit != bit) + { + while (symbol < 0x100) + { + symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); + } + + break; + } + } + while (symbol < 0x100); + return (byte)symbol; + } + } + + private Decoder2[] m_Coders; + private int m_NumPrevBits; + private int m_NumPosBits; + private uint m_PosMask; + + public void Create(int numPosBits, int numPrevBits) + { + if (m_Coders != null && m_NumPrevBits == numPrevBits && + m_NumPosBits == numPosBits) + { + return; + } + + m_NumPosBits = numPosBits; + m_PosMask = ((uint)1 << numPosBits) - 1; + m_NumPrevBits = numPrevBits; + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new Decoder2[numStates]; + for (uint i = 0; i < numStates; i++) + { + m_Coders[i].Create(); + } + } + + public void Init() + { + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + for (uint i = 0; i < numStates; i++) + { + m_Coders[i].Init(); + } + } + + private uint GetState(uint pos, byte prevByte) + { return ((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits)); } + + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) + { return m_Coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); } + + public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) + { return m_Coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); } + }; + + private readonly LZ.OutWindow m_OutWindow = new(); + private readonly RangeCoder.Decoder m_RangeDecoder = new(); + + private readonly BitDecoder[] m_IsMatchDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + private readonly BitDecoder[] m_IsRepDecoders = new BitDecoder[Base.kNumStates]; + private readonly BitDecoder[] m_IsRepG0Decoders = new BitDecoder[Base.kNumStates]; + private readonly BitDecoder[] m_IsRepG1Decoders = new BitDecoder[Base.kNumStates]; + private readonly BitDecoder[] m_IsRepG2Decoders = new BitDecoder[Base.kNumStates]; + private readonly BitDecoder[] m_IsRep0LongDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + + private readonly BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[Base.kNumLenToPosStates]; + private readonly BitDecoder[] m_PosDecoders = new BitDecoder[Base.kNumFullDistances - Base.kEndPosModelIndex]; + + private readonly BitTreeDecoder m_PosAlignDecoder = new(Base.kNumAlignBits); + + private readonly LenDecoder m_LenDecoder = new(); + private readonly LenDecoder m_RepLenDecoder = new(); + + private readonly LiteralDecoder m_LiteralDecoder = new(); + + private uint m_DictionarySize; + private uint m_DictionarySizeCheck; + + private uint m_PosStateMask; + + public Decoder() + { + m_DictionarySize = 0xFFFFFFFF; + for (int i = 0; i < Base.kNumLenToPosStates; i++) + { + m_PosSlotDecoder[i] = new BitTreeDecoder(Base.kNumPosSlotBits); + } + } + + private void SetDictionarySize(uint dictionarySize) + { + if (m_DictionarySize != dictionarySize) + { + m_DictionarySize = dictionarySize; + m_DictionarySizeCheck = Math.Max(m_DictionarySize, 1); + uint blockSize = Math.Max(m_DictionarySizeCheck, 1 << 12); + m_OutWindow.Create(blockSize); + } + } + + private void SetLiteralProperties(int lp, int lc) + { + if (lp > 8) + { + throw new InvalidParamException(); + } + + if (lc > 8) + { + throw new InvalidParamException(); + } + + m_LiteralDecoder.Create(lp, lc); + } + + private void SetPosBitsProperties(int pb) + { + if (pb > Base.kNumPosStatesBitsMax) + { + throw new InvalidParamException(); + } + + uint numPosStates = (uint)1 << pb; + m_LenDecoder.Create(numPosStates); + m_RepLenDecoder.Create(numPosStates); + m_PosStateMask = numPosStates - 1; + } + + private bool _solid = false; + private void Init(System.IO.Stream inStream, System.IO.Stream outStream) + { + m_RangeDecoder.Init(inStream); + m_OutWindow.Init(outStream, _solid); + + uint i; + for (i = 0; i < Base.kNumStates; i++) + { + for (uint j = 0; j <= m_PosStateMask; j++) + { + uint index = (i << Base.kNumPosStatesBitsMax) + j; + m_IsMatchDecoders[index].Init(); + m_IsRep0LongDecoders[index].Init(); + } + m_IsRepDecoders[i].Init(); + m_IsRepG0Decoders[i].Init(); + m_IsRepG1Decoders[i].Init(); + m_IsRepG2Decoders[i].Init(); + } + + m_LiteralDecoder.Init(); + for (i = 0; i < Base.kNumLenToPosStates; i++) + { + m_PosSlotDecoder[i].Init(); + } + // m_PosSpecDecoder.Init(); + for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++) + { + m_PosDecoders[i].Init(); + } + + m_LenDecoder.Init(); + m_RepLenDecoder.Init(); + m_PosAlignDecoder.Init(); + } + + public void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken? token = null) + { + Init(inStream, outStream); + + Base.State state = new(); + state.Init(); + uint rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0; + + UInt64 nowPos64 = 0; + UInt64 outSize64 = (UInt64)outSize; + if (nowPos64 < outSize64) + { + if (m_IsMatchDecoders[state.Index << Base.kNumPosStatesBitsMax].Decode(m_RangeDecoder) != 0) + { + throw new DataErrorException(); + } + + state.UpdateChar(); + byte b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, 0, 0); + m_OutWindow.PutByte(b); + nowPos64++; + } + + try + { + while (nowPos64 < outSize64) + { + token?.ThrowIfCancellationRequested(); + + // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64); + // while(nowPos64 < next) + { + uint posState = (uint)nowPos64 & m_PosStateMask; + if (m_IsMatchDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + byte b; + byte prevByte = m_OutWindow.GetByte(0); + b = !state.IsCharState() + ? m_LiteralDecoder.DecodeWithMatchByte(m_RangeDecoder, + (uint)nowPos64, prevByte, m_OutWindow.GetByte(rep0)) + : m_LiteralDecoder.DecodeNormal(m_RangeDecoder, (uint)nowPos64, prevByte); + + m_OutWindow.PutByte(b); + state.UpdateChar(); + nowPos64++; + } + else + { + uint len; + if (m_IsRepDecoders[state.Index].Decode(m_RangeDecoder) == 1) + { + if (m_IsRepG0Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + if (m_IsRep0LongDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + state.UpdateShortRep(); + m_OutWindow.PutByte(m_OutWindow.GetByte(rep0)); + nowPos64++; + continue; + } + } + else + { + UInt32 distance; + if (m_IsRepG1Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + distance = rep1; + } + else + { + if (m_IsRepG2Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + distance = rep2; + } + else + { + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + Base.kMatchMinLen; + state.UpdateRep(); + } + else + { + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + len = Base.kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState); + state.UpdateMatch(); + uint posSlot = m_PosSlotDecoder[Base.GetLenToPosState(len)].Decode(m_RangeDecoder); + if (posSlot >= Base.kStartPosModelIndex) + { + int numDirectBits = (int)((posSlot >> 1) - 1); + rep0 = (2 | (posSlot & 1)) << numDirectBits; + if (posSlot < Base.kEndPosModelIndex) + { + rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders, + rep0 - posSlot - 1, m_RangeDecoder, numDirectBits); + } + else + { + rep0 += m_RangeDecoder.DecodeDirectBits( + numDirectBits - Base.kNumAlignBits) << Base.kNumAlignBits; + rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder); + } + } + else + { + rep0 = posSlot; + } + } + if (rep0 >= m_OutWindow.TrainSize + nowPos64 || rep0 >= m_DictionarySizeCheck) + { + if (rep0 == 0xFFFFFFFF) + { + break; + } + + throw new DataErrorException(); + } + m_OutWindow.CopyBlock(rep0, len); + nowPos64 += len; + } + } + } + } + catch (OperationCanceledException) + { + } + + m_OutWindow.Flush(); + m_OutWindow.ReleaseStream(); + m_RangeDecoder.ReleaseStream(); + } + + public void SetDecoderProperties(byte[] properties) + { + if (properties.Length < 5) + { + throw new InvalidParamException(); + } + + int lc = properties[0] % 9; + int remainder = properties[0] / 9; + int lp = remainder % 5; + int pb = remainder / 5; + if (pb > Base.kNumPosStatesBitsMax) + { + throw new InvalidParamException(); + } + + UInt32 dictionarySize = 0; + for (int i = 0; i < 4; i++) + { + dictionarySize += ((UInt32)properties[1 + i]) << (i * 8); + } + + SetDictionarySize(dictionarySize); + SetLiteralProperties(lp, lc); + SetPosBitsProperties(pb); + } + + public bool Train(System.IO.Stream stream) + { + _solid = true; + return m_OutWindow.Train(stream); + } + + /* + public override bool CanRead { get { return true; }} + public override bool CanWrite { get { return true; }} + public override bool CanSeek { get { return true; }} + public override long Length { get { return 0; }} + public override long Position + { + get { return 0; } + set { } + } + public override void Flush() { } + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + public override void Write(byte[] buffer, int offset, int count) + { + } + public override long Seek(long offset, System.IO.SeekOrigin origin) + { + return 0; + } + public override void SetLength(long value) {} + */ + } +} diff --git a/7zip/Compress/LZMA/LzmaEncoder.cs b/WPinternals/7zip/Compress/LZMA/LzmaEncoder.cs similarity index 97% rename from 7zip/Compress/LZMA/LzmaEncoder.cs rename to WPinternals/7zip/Compress/LZMA/LzmaEncoder.cs index f1b8461..7d1dac9 100644 --- a/7zip/Compress/LZMA/LzmaEncoder.cs +++ b/WPinternals/7zip/Compress/LZMA/LzmaEncoder.cs @@ -1,1692 +1,1692 @@ -// LzmaEncoder.cs - -using System; - -namespace SevenZip.Compression.LZMA -{ - using RangeCoder; - using System.Threading; - - public class Encoder : ICoder, ISetCoderProperties, IWriteCoderProperties - { - private enum EMatchFinderType - { - BT2, - BT4, - }; - - private const UInt32 kIfinityPrice = 0xFFFFFFF; - - private static readonly Byte[] g_FastPos = new Byte[1 << 11]; - - static Encoder() - { - const Byte kFastSlots = 22; - int c = 2; - g_FastPos[0] = 0; - g_FastPos[1] = 1; - for (Byte slotFast = 2; slotFast < kFastSlots; slotFast++) - { - UInt32 k = (UInt32)1 << ((slotFast >> 1) - 1); - for (UInt32 j = 0; j < k; j++, c++) - { - g_FastPos[c] = slotFast; - } - } - } - - private static UInt32 GetPosSlot(UInt32 pos) - { - if (pos < (1 << 11)) - { - return g_FastPos[pos]; - } - - if (pos < (1 << 21)) - { - return (UInt32)(g_FastPos[pos >> 10] + 20); - } - - return (UInt32)(g_FastPos[pos >> 20] + 40); - } - - private static UInt32 GetPosSlot2(UInt32 pos) - { - if (pos < (1 << 17)) - { - return (UInt32)(g_FastPos[pos >> 6] + 12); - } - - if (pos < (1 << 27)) - { - return (UInt32)(g_FastPos[pos >> 16] + 32); - } - - return (UInt32)(g_FastPos[pos >> 26] + 52); - } - - private Base.State _state = new(); - private Byte _previousByte; - private readonly UInt32[] _repDistances = new UInt32[Base.kNumRepDistances]; - - private void BaseInit() - { - _state.Init(); - _previousByte = 0; - for (UInt32 i = 0; i < Base.kNumRepDistances; i++) - { - _repDistances[i] = 0; - } - } - - private const int kDefaultDictionaryLogSize = 22; - private const UInt32 kNumFastBytesDefault = 0x20; - - private class LiteralEncoder - { - public struct Encoder2 - { - private BitEncoder[] m_Encoders; - - public void Create() { m_Encoders = new BitEncoder[0x300]; } - - public void Init() { for (int i = 0; i < 0x300; i++) - { - m_Encoders[i].Init(); - } - } - - public void Encode(RangeCoder.Encoder rangeEncoder, byte symbol) - { - uint context = 1; - for (int i = 7; i >= 0; i--) - { - uint bit = (uint)((symbol >> i) & 1); - m_Encoders[context].Encode(rangeEncoder, bit); - context = (context << 1) | bit; - } - } - - public void EncodeMatched(RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol) - { - uint context = 1; - bool same = true; - for (int i = 7; i >= 0; i--) - { - uint bit = (uint)((symbol >> i) & 1); - uint state = context; - if (same) - { - uint matchBit = (uint)((matchByte >> i) & 1); - state += (1 + matchBit) << 8; - same = matchBit == bit; - } - m_Encoders[state].Encode(rangeEncoder, bit); - context = (context << 1) | bit; - } - } - - public uint GetPrice(bool matchMode, byte matchByte, byte symbol) - { - uint price = 0; - uint context = 1; - int i = 7; - if (matchMode) - { - for (; i >= 0; i--) - { - uint matchBit = (uint)(matchByte >> i) & 1; - uint bit = (uint)(symbol >> i) & 1; - price += m_Encoders[((1 + matchBit) << 8) + context].GetPrice(bit); - context = (context << 1) | bit; - if (matchBit != bit) - { - i--; - break; - } - } - } - for (; i >= 0; i--) - { - uint bit = (uint)(symbol >> i) & 1; - price += m_Encoders[context].GetPrice(bit); - context = (context << 1) | bit; - } - return price; - } - } - - private Encoder2[] m_Coders; - private int m_NumPrevBits; - private int m_NumPosBits; - private uint m_PosMask; - - public void Create(int numPosBits, int numPrevBits) - { - if (m_Coders != null && m_NumPrevBits == numPrevBits && m_NumPosBits == numPosBits) - { - return; - } - - m_NumPosBits = numPosBits; - m_PosMask = ((uint)1 << numPosBits) - 1; - m_NumPrevBits = numPrevBits; - uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); - m_Coders = new Encoder2[numStates]; - for (uint i = 0; i < numStates; i++) - { - m_Coders[i].Create(); - } - } - - public void Init() - { - uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); - for (uint i = 0; i < numStates; i++) - { - m_Coders[i].Init(); - } - } - - public Encoder2 GetSubCoder(UInt32 pos, Byte prevByte) - { return m_Coders[((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits))]; } - } - - private class LenEncoder - { - private BitEncoder _choice = new(); - private BitEncoder _choice2 = new(); - private readonly BitTreeEncoder[] _lowCoder = new BitTreeEncoder[Base.kNumPosStatesEncodingMax]; - private readonly BitTreeEncoder[] _midCoder = new BitTreeEncoder[Base.kNumPosStatesEncodingMax]; - private readonly BitTreeEncoder _highCoder = new(Base.kNumHighLenBits); - - public LenEncoder() - { - for (UInt32 posState = 0; posState < Base.kNumPosStatesEncodingMax; posState++) - { - _lowCoder[posState] = new BitTreeEncoder(Base.kNumLowLenBits); - _midCoder[posState] = new BitTreeEncoder(Base.kNumMidLenBits); - } - } - - public void Init(UInt32 numPosStates) - { - _choice.Init(); - _choice2.Init(); - for (UInt32 posState = 0; posState < numPosStates; posState++) - { - _lowCoder[posState].Init(); - _midCoder[posState].Init(); - } - _highCoder.Init(); - } - - public void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) - { - if (symbol < Base.kNumLowLenSymbols) - { - _choice.Encode(rangeEncoder, 0); - _lowCoder[posState].Encode(rangeEncoder, symbol); - } - else - { - symbol -= Base.kNumLowLenSymbols; - _choice.Encode(rangeEncoder, 1); - if (symbol < Base.kNumMidLenSymbols) - { - _choice2.Encode(rangeEncoder, 0); - _midCoder[posState].Encode(rangeEncoder, symbol); - } - else - { - _choice2.Encode(rangeEncoder, 1); - _highCoder.Encode(rangeEncoder, symbol - Base.kNumMidLenSymbols); - } - } - } - - public void SetPrices(UInt32 posState, UInt32 numSymbols, UInt32[] prices, UInt32 st) - { - UInt32 a0 = _choice.GetPrice0(); - UInt32 a1 = _choice.GetPrice1(); - UInt32 b0 = a1 + _choice2.GetPrice0(); - UInt32 b1 = a1 + _choice2.GetPrice1(); - UInt32 i = 0; - for (i = 0; i < Base.kNumLowLenSymbols; i++) - { - if (i >= numSymbols) - { - return; - } - - prices[st + i] = a0 + _lowCoder[posState].GetPrice(i); - } - for (; i < Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; i++) - { - if (i >= numSymbols) - { - return; - } - - prices[st + i] = b0 + _midCoder[posState].GetPrice(i - Base.kNumLowLenSymbols); - } - for (; i < numSymbols; i++) - { - prices[st + i] = b1 + _highCoder.GetPrice(i - Base.kNumLowLenSymbols - Base.kNumMidLenSymbols); - } - } - }; - - private const UInt32 kNumLenSpecSymbols = Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; - - private class LenPriceTableEncoder : LenEncoder - { - private readonly UInt32[] _prices = new UInt32[Base.kNumLenSymbols << Base.kNumPosStatesBitsEncodingMax]; - private UInt32 _tableSize; - private readonly UInt32[] _counters = new UInt32[Base.kNumPosStatesEncodingMax]; - - public void SetTableSize(UInt32 tableSize) { _tableSize = tableSize; } - - public UInt32 GetPrice(UInt32 symbol, UInt32 posState) - { - return _prices[(posState * Base.kNumLenSymbols) + symbol]; - } - - private void UpdateTable(UInt32 posState) - { - SetPrices(posState, _tableSize, _prices, posState * Base.kNumLenSymbols); - _counters[posState] = _tableSize; - } - - public void UpdateTables(UInt32 numPosStates) - { - for (UInt32 posState = 0; posState < numPosStates; posState++) - { - UpdateTable(posState); - } - } - - public new void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) - { - base.Encode(rangeEncoder, symbol, posState); - if (--_counters[posState] == 0) - { - UpdateTable(posState); - } - } - } - - private const UInt32 kNumOpts = 1 << 12; - private class Optimal - { - public Base.State State; - - public bool Prev1IsChar; - public bool Prev2; - - public UInt32 PosPrev2; - public UInt32 BackPrev2; - - public UInt32 Price; - public UInt32 PosPrev; - public UInt32 BackPrev; - - public UInt32 Backs0; - public UInt32 Backs1; - public UInt32 Backs2; - public UInt32 Backs3; - - public void MakeAsChar() { BackPrev = 0xFFFFFFFF; Prev1IsChar = false; } - public void MakeAsShortRep() { BackPrev = 0; Prev1IsChar = false; } - public bool IsShortRep() { return BackPrev == 0; } - }; - private readonly Optimal[] _optimum = new Optimal[kNumOpts]; - private LZ.IMatchFinder _matchFinder = null; - private readonly RangeCoder.Encoder _rangeEncoder = new(); - - private readonly BitEncoder[] _isMatch = new BitEncoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; - private readonly BitEncoder[] _isRep = new BitEncoder[Base.kNumStates]; - private readonly BitEncoder[] _isRepG0 = new BitEncoder[Base.kNumStates]; - private readonly BitEncoder[] _isRepG1 = new BitEncoder[Base.kNumStates]; - private readonly BitEncoder[] _isRepG2 = new BitEncoder[Base.kNumStates]; - private readonly BitEncoder[] _isRep0Long = new BitEncoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; - - private readonly BitTreeEncoder[] _posSlotEncoder = new BitTreeEncoder[Base.kNumLenToPosStates]; - - private readonly BitEncoder[] _posEncoders = new BitEncoder[Base.kNumFullDistances - Base.kEndPosModelIndex]; - private readonly BitTreeEncoder _posAlignEncoder = new(Base.kNumAlignBits); - - private readonly LenPriceTableEncoder _lenEncoder = new(); - private readonly LenPriceTableEncoder _repMatchLenEncoder = new(); - - private readonly LiteralEncoder _literalEncoder = new(); - - private readonly UInt32[] _matchDistances = new UInt32[(Base.kMatchMaxLen * 2) + 2]; - - private UInt32 _numFastBytes = kNumFastBytesDefault; - private UInt32 _longestMatchLength; - private UInt32 _numDistancePairs; - - private UInt32 _additionalOffset; - - private UInt32 _optimumEndIndex; - private UInt32 _optimumCurrentIndex; - - private bool _longestMatchWasFound; - - private readonly UInt32[] _posSlotPrices = new UInt32[1 << (Base.kNumPosSlotBits + Base.kNumLenToPosStatesBits)]; - private readonly UInt32[] _distancesPrices = new UInt32[Base.kNumFullDistances << Base.kNumLenToPosStatesBits]; - private readonly UInt32[] _alignPrices = new UInt32[Base.kAlignTableSize]; - private UInt32 _alignPriceCount; - - private UInt32 _distTableSize = kDefaultDictionaryLogSize * 2; - - private int _posStateBits = 2; - private UInt32 _posStateMask = 4 - 1; - private int _numLiteralPosStateBits = 0; - private int _numLiteralContextBits = 3; - - private UInt32 _dictionarySize = 1 << kDefaultDictionaryLogSize; - private UInt32 _dictionarySizePrev = 0xFFFFFFFF; - private UInt32 _numFastBytesPrev = 0xFFFFFFFF; - - private Int64 nowPos64; - private bool _finished; - private System.IO.Stream _inStream; - - private EMatchFinderType _matchFinderType = EMatchFinderType.BT4; - private bool _writeEndMark = false; - - private bool _needReleaseMFStream; - - private void Create() - { - if (_matchFinder == null) - { - LZ.BinTree bt = new(); - int numHashBytes = 4; - if (_matchFinderType == EMatchFinderType.BT2) - { - numHashBytes = 2; - } - - bt.SetType(numHashBytes); - _matchFinder = bt; - } - _literalEncoder.Create(_numLiteralPosStateBits, _numLiteralContextBits); - - if (_dictionarySize == _dictionarySizePrev && _numFastBytesPrev == _numFastBytes) - { - return; - } - - _matchFinder.Create(_dictionarySize, kNumOpts, _numFastBytes, Base.kMatchMaxLen + 1); - _dictionarySizePrev = _dictionarySize; - _numFastBytesPrev = _numFastBytes; - } - - public Encoder() - { - for (int i = 0; i < kNumOpts; i++) - { - _optimum[i] = new Optimal(); - } - - for (int i = 0; i < Base.kNumLenToPosStates; i++) - { - _posSlotEncoder[i] = new BitTreeEncoder(Base.kNumPosSlotBits); - } - } - - private void SetWriteEndMarkerMode(bool writeEndMarker) - { - _writeEndMark = writeEndMarker; - } - - private void Init() - { - BaseInit(); - _rangeEncoder.Init(); - - uint i; - for (i = 0; i < Base.kNumStates; i++) - { - for (uint j = 0; j <= _posStateMask; j++) - { - uint complexState = (i << Base.kNumPosStatesBitsMax) + j; - _isMatch[complexState].Init(); - _isRep0Long[complexState].Init(); - } - _isRep[i].Init(); - _isRepG0[i].Init(); - _isRepG1[i].Init(); - _isRepG2[i].Init(); - } - _literalEncoder.Init(); - for (i = 0; i < Base.kNumLenToPosStates; i++) - { - _posSlotEncoder[i].Init(); - } - - for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++) - { - _posEncoders[i].Init(); - } - - _lenEncoder.Init((UInt32)1 << _posStateBits); - _repMatchLenEncoder.Init((UInt32)1 << _posStateBits); - - _posAlignEncoder.Init(); - - _longestMatchWasFound = false; - _optimumEndIndex = 0; - _optimumCurrentIndex = 0; - _additionalOffset = 0; - } - - private void ReadMatchDistances(out UInt32 lenRes, out UInt32 numDistancePairs) - { - lenRes = 0; - numDistancePairs = _matchFinder.GetMatches(_matchDistances); - if (numDistancePairs > 0) - { - lenRes = _matchDistances[numDistancePairs - 2]; - if (lenRes == _numFastBytes) - { - lenRes += _matchFinder.GetMatchLen((int)lenRes - 1, _matchDistances[numDistancePairs - 1], - Base.kMatchMaxLen - lenRes); - } - } - _additionalOffset++; - } - - private void MovePos(UInt32 num) - { - if (num > 0) - { - _matchFinder.Skip(num); - _additionalOffset += num; - } - } - - private UInt32 GetRepLen1Price(Base.State state, UInt32 posState) - { - return _isRepG0[state.Index].GetPrice0() + - _isRep0Long[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0(); - } - - private UInt32 GetPureRepPrice(UInt32 repIndex, Base.State state, UInt32 posState) - { - UInt32 price; - if (repIndex == 0) - { - price = _isRepG0[state.Index].GetPrice0(); - price += _isRep0Long[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); - } - else - { - price = _isRepG0[state.Index].GetPrice1(); - if (repIndex == 1) - { - price += _isRepG1[state.Index].GetPrice0(); - } - else - { - price += _isRepG1[state.Index].GetPrice1(); - price += _isRepG2[state.Index].GetPrice(repIndex - 2); - } - } - return price; - } - - private UInt32 GetRepPrice(UInt32 repIndex, UInt32 len, Base.State state, UInt32 posState) - { - UInt32 price = _repMatchLenEncoder.GetPrice(len - Base.kMatchMinLen, posState); - return price + GetPureRepPrice(repIndex, state, posState); - } - - private UInt32 GetPosLenPrice(UInt32 pos, UInt32 len, UInt32 posState) - { - UInt32 price; - UInt32 lenToPosState = Base.GetLenToPosState(len); - price = pos < Base.kNumFullDistances - ? _distancesPrices[(lenToPosState * Base.kNumFullDistances) + pos] - : _posSlotPrices[(lenToPosState << Base.kNumPosSlotBits) + GetPosSlot2(pos)] + - _alignPrices[pos & Base.kAlignMask]; - - return price + _lenEncoder.GetPrice(len - Base.kMatchMinLen, posState); - } - - private UInt32 Backward(out UInt32 backRes, UInt32 cur) - { - _optimumEndIndex = cur; - UInt32 posMem = _optimum[cur].PosPrev; - UInt32 backMem = _optimum[cur].BackPrev; - do - { - if (_optimum[cur].Prev1IsChar) - { - _optimum[posMem].MakeAsChar(); - _optimum[posMem].PosPrev = posMem - 1; - if (_optimum[cur].Prev2) - { - _optimum[posMem - 1].Prev1IsChar = false; - _optimum[posMem - 1].PosPrev = _optimum[cur].PosPrev2; - _optimum[posMem - 1].BackPrev = _optimum[cur].BackPrev2; - } - } - UInt32 posPrev = posMem; - UInt32 backCur = backMem; - - backMem = _optimum[posPrev].BackPrev; - posMem = _optimum[posPrev].PosPrev; - - _optimum[posPrev].BackPrev = backCur; - _optimum[posPrev].PosPrev = cur; - cur = posPrev; - } - while (cur > 0); - backRes = _optimum[0].BackPrev; - _optimumCurrentIndex = _optimum[0].PosPrev; - return _optimumCurrentIndex; - } - - private readonly UInt32[] reps = new UInt32[Base.kNumRepDistances]; - private readonly UInt32[] repLens = new UInt32[Base.kNumRepDistances]; - - private UInt32 GetOptimum(UInt32 position, out UInt32 backRes) - { - if (_optimumEndIndex != _optimumCurrentIndex) - { - UInt32 lenRes = _optimum[_optimumCurrentIndex].PosPrev - _optimumCurrentIndex; - backRes = _optimum[_optimumCurrentIndex].BackPrev; - _optimumCurrentIndex = _optimum[_optimumCurrentIndex].PosPrev; - return lenRes; - } - _optimumCurrentIndex = _optimumEndIndex = 0; - - UInt32 lenMain, numDistancePairs; - if (!_longestMatchWasFound) - { - ReadMatchDistances(out lenMain, out numDistancePairs); - } - else - { - lenMain = _longestMatchLength; - numDistancePairs = _numDistancePairs; - _longestMatchWasFound = false; - } - - UInt32 numAvailableBytes = _matchFinder.GetNumAvailableBytes() + 1; - if (numAvailableBytes < 2) - { - backRes = 0xFFFFFFFF; - return 1; - } - if (numAvailableBytes > Base.kMatchMaxLen) - { - numAvailableBytes = Base.kMatchMaxLen; - } - - UInt32 repMaxIndex = 0; - UInt32 i; - for (i = 0; i < Base.kNumRepDistances; i++) - { - reps[i] = _repDistances[i]; - repLens[i] = _matchFinder.GetMatchLen(0 - 1, reps[i], Base.kMatchMaxLen); - if (repLens[i] > repLens[repMaxIndex]) - { - repMaxIndex = i; - } - } - if (repLens[repMaxIndex] >= _numFastBytes) - { - backRes = repMaxIndex; - UInt32 lenRes = repLens[repMaxIndex]; - MovePos(lenRes - 1); - return lenRes; - } - - if (lenMain >= _numFastBytes) - { - backRes = _matchDistances[numDistancePairs - 1] + Base.kNumRepDistances; - MovePos(lenMain - 1); - return lenMain; - } - - Byte currentByte = _matchFinder.GetIndexByte(0 - 1); - Byte matchByte = _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - 1)); - - if (lenMain < 2 && currentByte != matchByte && repLens[repMaxIndex] < 2) - { - backRes = 0xFFFFFFFF; - return 1; - } - - _optimum[0].State = _state; - - UInt32 posState = position & _posStateMask; - - _optimum[1].Price = _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0() + - _literalEncoder.GetSubCoder(position, _previousByte).GetPrice(!_state.IsCharState(), matchByte, currentByte); - _optimum[1].MakeAsChar(); - - UInt32 matchPrice = _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); - UInt32 repMatchPrice = matchPrice + _isRep[_state.Index].GetPrice1(); - - if (matchByte == currentByte) - { - UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(_state, posState); - if (shortRepPrice < _optimum[1].Price) - { - _optimum[1].Price = shortRepPrice; - _optimum[1].MakeAsShortRep(); - } - } - - UInt32 lenEnd = (lenMain >= repLens[repMaxIndex]) ? lenMain : repLens[repMaxIndex]; - - if (lenEnd < 2) - { - backRes = _optimum[1].BackPrev; - return 1; - } - - _optimum[1].PosPrev = 0; - - _optimum[0].Backs0 = reps[0]; - _optimum[0].Backs1 = reps[1]; - _optimum[0].Backs2 = reps[2]; - _optimum[0].Backs3 = reps[3]; - - UInt32 len = lenEnd; - do - { - _optimum[len--].Price = kIfinityPrice; - } - while (len >= 2); - - for (i = 0; i < Base.kNumRepDistances; i++) - { - UInt32 repLen = repLens[i]; - if (repLen < 2) - { - continue; - } - - UInt32 price = repMatchPrice + GetPureRepPrice(i, _state, posState); - do - { - UInt32 curAndLenPrice = price + _repMatchLenEncoder.GetPrice(repLen - 2, posState); - Optimal optimum = _optimum[repLen]; - if (curAndLenPrice < optimum.Price) - { - optimum.Price = curAndLenPrice; - optimum.PosPrev = 0; - optimum.BackPrev = i; - optimum.Prev1IsChar = false; - } - } - while (--repLen >= 2); - } - - UInt32 normalMatchPrice = matchPrice + _isRep[_state.Index].GetPrice0(); - - len = (repLens[0] >= 2) ? repLens[0] + 1 : 2; - if (len <= lenMain) - { - UInt32 offs = 0; - while (len > _matchDistances[offs]) - { - offs += 2; - } - - for (; ; len++) - { - UInt32 distance = _matchDistances[offs + 1]; - UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(distance, len, posState); - Optimal optimum = _optimum[len]; - if (curAndLenPrice < optimum.Price) - { - optimum.Price = curAndLenPrice; - optimum.PosPrev = 0; - optimum.BackPrev = distance + Base.kNumRepDistances; - optimum.Prev1IsChar = false; - } - if (len == _matchDistances[offs]) - { - offs += 2; - if (offs == numDistancePairs) - { - break; - } - } - } - } - - UInt32 cur = 0; - - while (true) - { - cur++; - if (cur == lenEnd) - { - return Backward(out backRes, cur); - } - - ReadMatchDistances(out uint newLen, out numDistancePairs); - if (newLen >= _numFastBytes) - { - _numDistancePairs = numDistancePairs; - _longestMatchLength = newLen; - _longestMatchWasFound = true; - return Backward(out backRes, cur); - } - position++; - UInt32 posPrev = _optimum[cur].PosPrev; - Base.State state; - if (_optimum[cur].Prev1IsChar) - { - posPrev--; - if (_optimum[cur].Prev2) - { - state = _optimum[_optimum[cur].PosPrev2].State; - if (_optimum[cur].BackPrev2 < Base.kNumRepDistances) - { - state.UpdateRep(); - } - else - { - state.UpdateMatch(); - } - } - else - { - state = _optimum[posPrev].State; - } - - state.UpdateChar(); - } - else - { - state = _optimum[posPrev].State; - } - - if (posPrev == cur - 1) - { - if (_optimum[cur].IsShortRep()) - { - state.UpdateShortRep(); - } - else - { - state.UpdateChar(); - } - } - else - { - UInt32 pos; - if (_optimum[cur].Prev1IsChar && _optimum[cur].Prev2) - { - posPrev = _optimum[cur].PosPrev2; - pos = _optimum[cur].BackPrev2; - state.UpdateRep(); - } - else - { - pos = _optimum[cur].BackPrev; - if (pos < Base.kNumRepDistances) - { - state.UpdateRep(); - } - else - { - state.UpdateMatch(); - } - } - Optimal opt = _optimum[posPrev]; - if (pos < Base.kNumRepDistances) - { - if (pos == 0) - { - reps[0] = opt.Backs0; - reps[1] = opt.Backs1; - reps[2] = opt.Backs2; - reps[3] = opt.Backs3; - } - else if (pos == 1) - { - reps[0] = opt.Backs1; - reps[1] = opt.Backs0; - reps[2] = opt.Backs2; - reps[3] = opt.Backs3; - } - else if (pos == 2) - { - reps[0] = opt.Backs2; - reps[1] = opt.Backs0; - reps[2] = opt.Backs1; - reps[3] = opt.Backs3; - } - else - { - reps[0] = opt.Backs3; - reps[1] = opt.Backs0; - reps[2] = opt.Backs1; - reps[3] = opt.Backs2; - } - } - else - { - reps[0] = pos - Base.kNumRepDistances; - reps[1] = opt.Backs0; - reps[2] = opt.Backs1; - reps[3] = opt.Backs2; - } - } - _optimum[cur].State = state; - _optimum[cur].Backs0 = reps[0]; - _optimum[cur].Backs1 = reps[1]; - _optimum[cur].Backs2 = reps[2]; - _optimum[cur].Backs3 = reps[3]; - UInt32 curPrice = _optimum[cur].Price; - - currentByte = _matchFinder.GetIndexByte(0 - 1); - matchByte = _matchFinder.GetIndexByte((Int32)(0 - reps[0] - 1 - 1)); - - posState = position & _posStateMask; - - UInt32 curAnd1Price = curPrice + - _isMatch[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0() + - _literalEncoder.GetSubCoder(position, _matchFinder.GetIndexByte(0 - 2)). - GetPrice(!state.IsCharState(), matchByte, currentByte); - - Optimal nextOptimum = _optimum[cur + 1]; - - bool nextIsChar = false; - if (curAnd1Price < nextOptimum.Price) - { - nextOptimum.Price = curAnd1Price; - nextOptimum.PosPrev = cur; - nextOptimum.MakeAsChar(); - nextIsChar = true; - } - - matchPrice = curPrice + _isMatch[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); - repMatchPrice = matchPrice + _isRep[state.Index].GetPrice1(); - - if (matchByte == currentByte && - !(nextOptimum.PosPrev < cur && nextOptimum.BackPrev == 0)) - { - UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(state, posState); - if (shortRepPrice <= nextOptimum.Price) - { - nextOptimum.Price = shortRepPrice; - nextOptimum.PosPrev = cur; - nextOptimum.MakeAsShortRep(); - nextIsChar = true; - } - } - - UInt32 numAvailableBytesFull = _matchFinder.GetNumAvailableBytes() + 1; - numAvailableBytesFull = Math.Min(kNumOpts - 1 - cur, numAvailableBytesFull); - numAvailableBytes = numAvailableBytesFull; - - if (numAvailableBytes < 2) - { - continue; - } - - if (numAvailableBytes > _numFastBytes) - { - numAvailableBytes = _numFastBytes; - } - - if (!nextIsChar && matchByte != currentByte) - { - // try Literal + rep0 - UInt32 t = Math.Min(numAvailableBytesFull - 1, _numFastBytes); - UInt32 lenTest2 = _matchFinder.GetMatchLen(0, reps[0], t); - if (lenTest2 >= 2) - { - Base.State state2 = state; - state2.UpdateChar(); - UInt32 posStateNext = (position + 1) & _posStateMask; - UInt32 nextRepMatchPrice = curAnd1Price + - _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1() + - _isRep[state2.Index].GetPrice1(); - { - UInt32 offset = cur + 1 + lenTest2; - while (lenEnd < offset) - { - _optimum[++lenEnd].Price = kIfinityPrice; - } - - UInt32 curAndLenPrice = nextRepMatchPrice + GetRepPrice( - 0, lenTest2, state2, posStateNext); - Optimal optimum = _optimum[offset]; - if (curAndLenPrice < optimum.Price) - { - optimum.Price = curAndLenPrice; - optimum.PosPrev = cur + 1; - optimum.BackPrev = 0; - optimum.Prev1IsChar = true; - optimum.Prev2 = false; - } - } - } - } - - UInt32 startLen = 2; // speed optimization - - for (UInt32 repIndex = 0; repIndex < Base.kNumRepDistances; repIndex++) - { - UInt32 lenTest = _matchFinder.GetMatchLen(0 - 1, reps[repIndex], numAvailableBytes); - if (lenTest < 2) - { - continue; - } - - UInt32 lenTestTemp = lenTest; - do - { - while (lenEnd < cur + lenTest) - { - _optimum[++lenEnd].Price = kIfinityPrice; - } - - UInt32 curAndLenPrice = repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState); - Optimal optimum = _optimum[cur + lenTest]; - if (curAndLenPrice < optimum.Price) - { - optimum.Price = curAndLenPrice; - optimum.PosPrev = cur; - optimum.BackPrev = repIndex; - optimum.Prev1IsChar = false; - } - } - while (--lenTest >= 2); - lenTest = lenTestTemp; - - if (repIndex == 0) - { - startLen = lenTest + 1; - } - - // if (_maxMode) - if (lenTest < numAvailableBytesFull) - { - UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); - UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, reps[repIndex], t); - if (lenTest2 >= 2) - { - Base.State state2 = state; - state2.UpdateRep(); - UInt32 posStateNext = (position + lenTest) & _posStateMask; - UInt32 curAndLenCharPrice = - repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState) + - _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice0() + - _literalEncoder.GetSubCoder(position + lenTest, - _matchFinder.GetIndexByte((Int32)lenTest - 1 - 1)).GetPrice(true, - _matchFinder.GetIndexByte((Int32)lenTest - 1 - (Int32)(reps[repIndex] + 1)), - _matchFinder.GetIndexByte((Int32)lenTest - 1)); - state2.UpdateChar(); - posStateNext = (position + lenTest + 1) & _posStateMask; - UInt32 nextMatchPrice = curAndLenCharPrice + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1(); - UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2.Index].GetPrice1(); - - // for(; lenTest2 >= 2; lenTest2--) - { - UInt32 offset = lenTest + 1 + lenTest2; - while (lenEnd < cur + offset) - { - _optimum[++lenEnd].Price = kIfinityPrice; - } - - UInt32 curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); - Optimal optimum = _optimum[cur + offset]; - if (curAndLenPrice < optimum.Price) - { - optimum.Price = curAndLenPrice; - optimum.PosPrev = cur + lenTest + 1; - optimum.BackPrev = 0; - optimum.Prev1IsChar = true; - optimum.Prev2 = true; - optimum.PosPrev2 = cur; - optimum.BackPrev2 = repIndex; - } - } - } - } - } - - if (newLen > numAvailableBytes) - { - newLen = numAvailableBytes; - for (numDistancePairs = 0; newLen > _matchDistances[numDistancePairs]; numDistancePairs += 2) - { - } - - _matchDistances[numDistancePairs] = newLen; - numDistancePairs += 2; - } - if (newLen >= startLen) - { - normalMatchPrice = matchPrice + _isRep[state.Index].GetPrice0(); - while (lenEnd < cur + newLen) - { - _optimum[++lenEnd].Price = kIfinityPrice; - } - - UInt32 offs = 0; - while (startLen > _matchDistances[offs]) - { - offs += 2; - } - - for (UInt32 lenTest = startLen; ; lenTest++) - { - UInt32 curBack = _matchDistances[offs + 1]; - UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(curBack, lenTest, posState); - Optimal optimum = _optimum[cur + lenTest]; - if (curAndLenPrice < optimum.Price) - { - optimum.Price = curAndLenPrice; - optimum.PosPrev = cur; - optimum.BackPrev = curBack + Base.kNumRepDistances; - optimum.Prev1IsChar = false; - } - - if (lenTest == _matchDistances[offs]) - { - if (lenTest < numAvailableBytesFull) - { - UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); - UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, curBack, t); - if (lenTest2 >= 2) - { - Base.State state2 = state; - state2.UpdateMatch(); - UInt32 posStateNext = (position + lenTest) & _posStateMask; - UInt32 curAndLenCharPrice = curAndLenPrice + - _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice0() + - _literalEncoder.GetSubCoder(position + lenTest, - _matchFinder.GetIndexByte((Int32)lenTest - 1 - 1)). - GetPrice(true, - _matchFinder.GetIndexByte((Int32)lenTest - (Int32)(curBack + 1) - 1), - _matchFinder.GetIndexByte((Int32)lenTest - 1)); - state2.UpdateChar(); - posStateNext = (position + lenTest + 1) & _posStateMask; - UInt32 nextMatchPrice = curAndLenCharPrice + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1(); - UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2.Index].GetPrice1(); - - UInt32 offset = lenTest + 1 + lenTest2; - while (lenEnd < cur + offset) - { - _optimum[++lenEnd].Price = kIfinityPrice; - } - - curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); - optimum = _optimum[cur + offset]; - if (curAndLenPrice < optimum.Price) - { - optimum.Price = curAndLenPrice; - optimum.PosPrev = cur + lenTest + 1; - optimum.BackPrev = 0; - optimum.Prev1IsChar = true; - optimum.Prev2 = true; - optimum.PosPrev2 = cur; - optimum.BackPrev2 = curBack + Base.kNumRepDistances; - } - } - } - offs += 2; - if (offs == numDistancePairs) - { - break; - } - } - } - } - } - } - - private bool ChangePair(UInt32 smallDist, UInt32 bigDist) - { - const int kDif = 7; - return smallDist < ((UInt32)1 << (32 - kDif)) && bigDist >= (smallDist << kDif); - } - - private void WriteEndMarker(UInt32 posState) - { - if (!_writeEndMark) - { - return; - } - - _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].Encode(_rangeEncoder, 1); - _isRep[_state.Index].Encode(_rangeEncoder, 0); - _state.UpdateMatch(); - const UInt32 len = Base.kMatchMinLen; - _lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); - const UInt32 posSlot = (1 << Base.kNumPosSlotBits) - 1; - UInt32 lenToPosState = Base.GetLenToPosState(len); - _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); - const int footerBits = 30; - const UInt32 posReduced = (((UInt32)1) << footerBits) - 1; - _rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits); - _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask); - } - - private void Flush(UInt32 nowPos) - { - ReleaseMFStream(); - WriteEndMarker(nowPos & _posStateMask); - _rangeEncoder.FlushData(); - _rangeEncoder.FlushStream(); - } - - public void CodeOneBlock(out Int64 inSize, out Int64 outSize, out bool finished) - { - inSize = 0; - outSize = 0; - finished = true; - - if (_inStream != null) - { - _matchFinder.SetStream(_inStream); - _matchFinder.Init(); - _needReleaseMFStream = true; - _inStream = null; - if (_trainSize > 0) - { - _matchFinder.Skip(_trainSize); - } - } - - if (_finished) - { - return; - } - - _finished = true; - - Int64 progressPosValuePrev = nowPos64; - if (nowPos64 == 0) - { - if (_matchFinder.GetNumAvailableBytes() == 0) - { - Flush((UInt32)nowPos64); - return; - } - // it's not used - ReadMatchDistances(out uint len, out uint numDistancePairs); - UInt32 posState = (UInt32)nowPos64 & _posStateMask; - _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].Encode(_rangeEncoder, 0); - _state.UpdateChar(); - Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); - _literalEncoder.GetSubCoder((UInt32)nowPos64, _previousByte).Encode(_rangeEncoder, curByte); - _previousByte = curByte; - _additionalOffset--; - nowPos64++; - } - if (_matchFinder.GetNumAvailableBytes() == 0) - { - Flush((UInt32)nowPos64); - return; - } - while (true) - { - UInt32 len = GetOptimum((UInt32)nowPos64, out uint pos); - - UInt32 posState = ((UInt32)nowPos64) & _posStateMask; - UInt32 complexState = (_state.Index << Base.kNumPosStatesBitsMax) + posState; - if (len == 1 && pos == 0xFFFFFFFF) - { - _isMatch[complexState].Encode(_rangeEncoder, 0); - Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); - LiteralEncoder.Encoder2 subCoder = _literalEncoder.GetSubCoder((UInt32)nowPos64, _previousByte); - if (!_state.IsCharState()) - { - Byte matchByte = _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - _additionalOffset)); - subCoder.EncodeMatched(_rangeEncoder, matchByte, curByte); - } - else - { - subCoder.Encode(_rangeEncoder, curByte); - } - - _previousByte = curByte; - _state.UpdateChar(); - } - else - { - _isMatch[complexState].Encode(_rangeEncoder, 1); - if (pos < Base.kNumRepDistances) - { - _isRep[_state.Index].Encode(_rangeEncoder, 1); - if (pos == 0) - { - _isRepG0[_state.Index].Encode(_rangeEncoder, 0); - if (len == 1) - { - _isRep0Long[complexState].Encode(_rangeEncoder, 0); - } - else - { - _isRep0Long[complexState].Encode(_rangeEncoder, 1); - } - } - else - { - _isRepG0[_state.Index].Encode(_rangeEncoder, 1); - if (pos == 1) - { - _isRepG1[_state.Index].Encode(_rangeEncoder, 0); - } - else - { - _isRepG1[_state.Index].Encode(_rangeEncoder, 1); - _isRepG2[_state.Index].Encode(_rangeEncoder, pos - 2); - } - } - if (len == 1) - { - _state.UpdateShortRep(); - } - else - { - _repMatchLenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); - _state.UpdateRep(); - } - UInt32 distance = _repDistances[pos]; - if (pos != 0) - { - for (UInt32 i = pos; i >= 1; i--) - { - _repDistances[i] = _repDistances[i - 1]; - } - - _repDistances[0] = distance; - } - } - else - { - _isRep[_state.Index].Encode(_rangeEncoder, 0); - _state.UpdateMatch(); - _lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); - pos -= Base.kNumRepDistances; - UInt32 posSlot = GetPosSlot(pos); - UInt32 lenToPosState = Base.GetLenToPosState(len); - _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); - - if (posSlot >= Base.kStartPosModelIndex) - { - int footerBits = (int)((posSlot >> 1) - 1); - UInt32 baseVal = (2 | (posSlot & 1)) << footerBits; - UInt32 posReduced = pos - baseVal; - - if (posSlot < Base.kEndPosModelIndex) - { - BitTreeEncoder.ReverseEncode(_posEncoders, - baseVal - posSlot - 1, _rangeEncoder, footerBits, posReduced); - } - else - { - _rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits); - _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask); - _alignPriceCount++; - } - } - UInt32 distance = pos; - for (UInt32 i = Base.kNumRepDistances - 1; i >= 1; i--) - { - _repDistances[i] = _repDistances[i - 1]; - } - - _repDistances[0] = distance; - _matchPriceCount++; - } - _previousByte = _matchFinder.GetIndexByte((Int32)(len - 1 - _additionalOffset)); - } - _additionalOffset -= len; - nowPos64 += len; - if (_additionalOffset == 0) - { - // if (!_fastMode) - if (_matchPriceCount >= (1 << 7)) - { - FillDistancesPrices(); - } - - if (_alignPriceCount >= Base.kAlignTableSize) - { - FillAlignPrices(); - } - - inSize = nowPos64; - outSize = _rangeEncoder.GetProcessedSizeAdd(); - if (_matchFinder.GetNumAvailableBytes() == 0) - { - Flush((UInt32)nowPos64); - return; - } - - if (nowPos64 - progressPosValuePrev >= (1 << 12)) - { - _finished = false; - finished = false; - return; - } - } - } - } - - private void ReleaseMFStream() - { - if (_matchFinder != null && _needReleaseMFStream) - { - _matchFinder.ReleaseStream(); - _needReleaseMFStream = false; - } - } - - private void SetOutStream(System.IO.Stream outStream) { _rangeEncoder.SetStream(outStream); } - private void ReleaseOutStream() { _rangeEncoder.ReleaseStream(); } - - private void ReleaseStreams() - { - ReleaseMFStream(); - ReleaseOutStream(); - } - - private void SetStreams(System.IO.Stream inStream, System.IO.Stream outStream, - Int64 inSize, Int64 outSize) - { - _inStream = inStream; - _finished = false; - Create(); - SetOutStream(outStream); - Init(); - - // if (!_fastMode) - { - FillDistancesPrices(); - FillAlignPrices(); - } - - _lenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen); - _lenEncoder.UpdateTables((UInt32)1 << _posStateBits); - _repMatchLenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen); - _repMatchLenEncoder.UpdateTables((UInt32)1 << _posStateBits); - - nowPos64 = 0; - } - - public void Code(System.IO.Stream inStream, System.IO.Stream outStream, - Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken? token = null) - { - _needReleaseMFStream = false; - try - { - SetStreams(inStream, outStream, inSize, outSize); - while (true) - { - token?.ThrowIfCancellationRequested(); - CodeOneBlock(out long processedInSize, out long processedOutSize, out bool finished); - if (finished) - { - return; - } - - progress?.SetProgress(processedInSize, processedOutSize); - } - } - finally - { - ReleaseStreams(); - } - } - - private const int kPropSize = 5; - private readonly Byte[] properties = new Byte[kPropSize]; - - public void WriteCoderProperties(System.IO.Stream outStream) - { - properties[0] = (Byte)((((_posStateBits * 5) + _numLiteralPosStateBits) * 9) + _numLiteralContextBits); - for (int i = 0; i < 4; i++) - { - properties[1 + i] = (Byte)((_dictionarySize >> (8 * i)) & 0xFF); - } - - outStream.Write(properties, 0, kPropSize); - } - - private readonly UInt32[] tempPrices = new UInt32[Base.kNumFullDistances]; - private UInt32 _matchPriceCount; - - private void FillDistancesPrices() - { - for (UInt32 i = Base.kStartPosModelIndex; i < Base.kNumFullDistances; i++) - { - UInt32 posSlot = GetPosSlot(i); - int footerBits = (int)((posSlot >> 1) - 1); - UInt32 baseVal = (2 | (posSlot & 1)) << footerBits; - tempPrices[i] = BitTreeEncoder.ReverseGetPrice(_posEncoders, - baseVal - posSlot - 1, footerBits, i - baseVal); - } - - for (UInt32 lenToPosState = 0; lenToPosState < Base.kNumLenToPosStates; lenToPosState++) - { - UInt32 posSlot; - BitTreeEncoder encoder = _posSlotEncoder[lenToPosState]; - - UInt32 st = lenToPosState << Base.kNumPosSlotBits; - for (posSlot = 0; posSlot < _distTableSize; posSlot++) - { - _posSlotPrices[st + posSlot] = encoder.GetPrice(posSlot); - } - - for (posSlot = Base.kEndPosModelIndex; posSlot < _distTableSize; posSlot++) - { - _posSlotPrices[st + posSlot] += ((posSlot >> 1) - 1 - Base.kNumAlignBits) << BitEncoder.kNumBitPriceShiftBits; - } - - UInt32 st2 = lenToPosState * Base.kNumFullDistances; - UInt32 i; - for (i = 0; i < Base.kStartPosModelIndex; i++) - { - _distancesPrices[st2 + i] = _posSlotPrices[st + i]; - } - - for (; i < Base.kNumFullDistances; i++) - { - _distancesPrices[st2 + i] = _posSlotPrices[st + GetPosSlot(i)] + tempPrices[i]; - } - } - _matchPriceCount = 0; - } - - private void FillAlignPrices() - { - for (UInt32 i = 0; i < Base.kAlignTableSize; i++) - { - _alignPrices[i] = _posAlignEncoder.ReverseGetPrice(i); - } - - _alignPriceCount = 0; - } - - private static readonly string[] kMatchFinderIDs = - { - "BT2", - "BT4", - }; - - private static int FindMatchFinder(string s) - { - for (int m = 0; m < kMatchFinderIDs.Length; m++) - { - if (s == kMatchFinderIDs[m]) - { - return m; - } - } - - return -1; - } - - public void SetCoderProperties(CoderPropID[] propIDs, object[] properties) - { - for (UInt32 i = 0; i < properties.Length; i++) - { - object prop = properties[i]; - switch (propIDs[i]) - { - case CoderPropID.NumFastBytes: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - - Int32 numFastBytes = (Int32)prop; - if (numFastBytes < 5 || numFastBytes > Base.kMatchMaxLen) - { - throw new InvalidParamException(); - } - - _numFastBytes = (UInt32)numFastBytes; - break; - } - case CoderPropID.Algorithm: - { - /* - if (!(prop is Int32)) - throw new InvalidParamException(); - Int32 maximize = (Int32)prop; - _fastMode = (maximize == 0); - _maxMode = (maximize >= 2); - */ - break; - } - case CoderPropID.MatchFinder: - { - if (!(prop is String)) - { - throw new InvalidParamException(); - } - - EMatchFinderType matchFinderIndexPrev = _matchFinderType; - int m = FindMatchFinder(((string)prop).ToUpper()); - if (m < 0) - { - throw new InvalidParamException(); - } - - _matchFinderType = (EMatchFinderType)m; - if (_matchFinder != null && matchFinderIndexPrev != _matchFinderType) - { - _dictionarySizePrev = 0xFFFFFFFF; - _matchFinder = null; - } - break; - } - case CoderPropID.DictionarySize: - { - const int kDicLogSizeMaxCompress = 30; - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - Int32 dictionarySize = (Int32)prop; - if (dictionarySize < (UInt32)(1 << Base.kDicLogSizeMin) || - dictionarySize > (UInt32)(1 << kDicLogSizeMaxCompress)) - { - throw new InvalidParamException(); - } - - _dictionarySize = (UInt32)dictionarySize; - int dicLogSize; - for (dicLogSize = 0; dicLogSize < (UInt32)kDicLogSizeMaxCompress; dicLogSize++) - { - if (dictionarySize <= ((UInt32)1 << dicLogSize)) - { - break; - } - } - - _distTableSize = (UInt32)dicLogSize * 2; - break; - } - case CoderPropID.PosStateBits: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - - Int32 v = (Int32)prop; - if (v < 0 || v > (UInt32)Base.kNumPosStatesBitsEncodingMax) - { - throw new InvalidParamException(); - } - - _posStateBits = v; - _posStateMask = (((UInt32)1) << _posStateBits) - 1; - break; - } - case CoderPropID.LitPosBits: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - - Int32 v = (Int32)prop; - if (v < 0 || v > Base.kNumLitPosStatesBitsEncodingMax) - { - throw new InvalidParamException(); - } - - _numLiteralPosStateBits = v; - break; - } - case CoderPropID.LitContextBits: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - - Int32 v = (Int32)prop; - if (v < 0 || v > Base.kNumLitContextBitsMax) - { - throw new InvalidParamException(); - } - _numLiteralContextBits = v; - break; - } - case CoderPropID.EndMarker: - { - if (!(prop is Boolean)) - { - throw new InvalidParamException(); - } - - SetWriteEndMarkerMode((Boolean)prop); - break; - } - default: - throw new InvalidParamException(); - } - } - } - - private uint _trainSize = 0; - public void SetTrainSize(uint trainSize) - { - _trainSize = trainSize; - } - } -} +// LzmaEncoder.cs + +using System; + +namespace SevenZip.Compression.LZMA +{ + using RangeCoder; + using System.Threading; + + public class Encoder : ICoder, ISetCoderProperties, IWriteCoderProperties + { + private enum EMatchFinderType + { + BT2, + BT4, + }; + + private const UInt32 kIfinityPrice = 0xFFFFFFF; + + private static readonly Byte[] g_FastPos = new Byte[1 << 11]; + + static Encoder() + { + const Byte kFastSlots = 22; + int c = 2; + g_FastPos[0] = 0; + g_FastPos[1] = 1; + for (Byte slotFast = 2; slotFast < kFastSlots; slotFast++) + { + UInt32 k = (UInt32)1 << ((slotFast >> 1) - 1); + for (UInt32 j = 0; j < k; j++, c++) + { + g_FastPos[c] = slotFast; + } + } + } + + private static UInt32 GetPosSlot(UInt32 pos) + { + if (pos < (1 << 11)) + { + return g_FastPos[pos]; + } + + if (pos < (1 << 21)) + { + return (UInt32)(g_FastPos[pos >> 10] + 20); + } + + return (UInt32)(g_FastPos[pos >> 20] + 40); + } + + private static UInt32 GetPosSlot2(UInt32 pos) + { + if (pos < (1 << 17)) + { + return (UInt32)(g_FastPos[pos >> 6] + 12); + } + + if (pos < (1 << 27)) + { + return (UInt32)(g_FastPos[pos >> 16] + 32); + } + + return (UInt32)(g_FastPos[pos >> 26] + 52); + } + + private Base.State _state = new(); + private Byte _previousByte; + private readonly UInt32[] _repDistances = new UInt32[Base.kNumRepDistances]; + + private void BaseInit() + { + _state.Init(); + _previousByte = 0; + for (UInt32 i = 0; i < Base.kNumRepDistances; i++) + { + _repDistances[i] = 0; + } + } + + private const int kDefaultDictionaryLogSize = 22; + private const UInt32 kNumFastBytesDefault = 0x20; + + private class LiteralEncoder + { + public struct Encoder2 + { + private BitEncoder[] m_Encoders; + + public void Create() { m_Encoders = new BitEncoder[0x300]; } + + public void Init() { for (int i = 0; i < 0x300; i++) + { + m_Encoders[i].Init(); + } + } + + public void Encode(RangeCoder.Encoder rangeEncoder, byte symbol) + { + uint context = 1; + for (int i = 7; i >= 0; i--) + { + uint bit = (uint)((symbol >> i) & 1); + m_Encoders[context].Encode(rangeEncoder, bit); + context = (context << 1) | bit; + } + } + + public void EncodeMatched(RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol) + { + uint context = 1; + bool same = true; + for (int i = 7; i >= 0; i--) + { + uint bit = (uint)((symbol >> i) & 1); + uint state = context; + if (same) + { + uint matchBit = (uint)((matchByte >> i) & 1); + state += (1 + matchBit) << 8; + same = matchBit == bit; + } + m_Encoders[state].Encode(rangeEncoder, bit); + context = (context << 1) | bit; + } + } + + public uint GetPrice(bool matchMode, byte matchByte, byte symbol) + { + uint price = 0; + uint context = 1; + int i = 7; + if (matchMode) + { + for (; i >= 0; i--) + { + uint matchBit = (uint)(matchByte >> i) & 1; + uint bit = (uint)(symbol >> i) & 1; + price += m_Encoders[((1 + matchBit) << 8) + context].GetPrice(bit); + context = (context << 1) | bit; + if (matchBit != bit) + { + i--; + break; + } + } + } + for (; i >= 0; i--) + { + uint bit = (uint)(symbol >> i) & 1; + price += m_Encoders[context].GetPrice(bit); + context = (context << 1) | bit; + } + return price; + } + } + + private Encoder2[] m_Coders; + private int m_NumPrevBits; + private int m_NumPosBits; + private uint m_PosMask; + + public void Create(int numPosBits, int numPrevBits) + { + if (m_Coders != null && m_NumPrevBits == numPrevBits && m_NumPosBits == numPosBits) + { + return; + } + + m_NumPosBits = numPosBits; + m_PosMask = ((uint)1 << numPosBits) - 1; + m_NumPrevBits = numPrevBits; + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new Encoder2[numStates]; + for (uint i = 0; i < numStates; i++) + { + m_Coders[i].Create(); + } + } + + public void Init() + { + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + for (uint i = 0; i < numStates; i++) + { + m_Coders[i].Init(); + } + } + + public Encoder2 GetSubCoder(UInt32 pos, Byte prevByte) + { return m_Coders[((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits))]; } + } + + private class LenEncoder + { + private BitEncoder _choice = new(); + private BitEncoder _choice2 = new(); + private readonly BitTreeEncoder[] _lowCoder = new BitTreeEncoder[Base.kNumPosStatesEncodingMax]; + private readonly BitTreeEncoder[] _midCoder = new BitTreeEncoder[Base.kNumPosStatesEncodingMax]; + private readonly BitTreeEncoder _highCoder = new(Base.kNumHighLenBits); + + public LenEncoder() + { + for (UInt32 posState = 0; posState < Base.kNumPosStatesEncodingMax; posState++) + { + _lowCoder[posState] = new BitTreeEncoder(Base.kNumLowLenBits); + _midCoder[posState] = new BitTreeEncoder(Base.kNumMidLenBits); + } + } + + public void Init(UInt32 numPosStates) + { + _choice.Init(); + _choice2.Init(); + for (UInt32 posState = 0; posState < numPosStates; posState++) + { + _lowCoder[posState].Init(); + _midCoder[posState].Init(); + } + _highCoder.Init(); + } + + public void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) + { + if (symbol < Base.kNumLowLenSymbols) + { + _choice.Encode(rangeEncoder, 0); + _lowCoder[posState].Encode(rangeEncoder, symbol); + } + else + { + symbol -= Base.kNumLowLenSymbols; + _choice.Encode(rangeEncoder, 1); + if (symbol < Base.kNumMidLenSymbols) + { + _choice2.Encode(rangeEncoder, 0); + _midCoder[posState].Encode(rangeEncoder, symbol); + } + else + { + _choice2.Encode(rangeEncoder, 1); + _highCoder.Encode(rangeEncoder, symbol - Base.kNumMidLenSymbols); + } + } + } + + public void SetPrices(UInt32 posState, UInt32 numSymbols, UInt32[] prices, UInt32 st) + { + UInt32 a0 = _choice.GetPrice0(); + UInt32 a1 = _choice.GetPrice1(); + UInt32 b0 = a1 + _choice2.GetPrice0(); + UInt32 b1 = a1 + _choice2.GetPrice1(); + UInt32 i = 0; + for (i = 0; i < Base.kNumLowLenSymbols; i++) + { + if (i >= numSymbols) + { + return; + } + + prices[st + i] = a0 + _lowCoder[posState].GetPrice(i); + } + for (; i < Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; i++) + { + if (i >= numSymbols) + { + return; + } + + prices[st + i] = b0 + _midCoder[posState].GetPrice(i - Base.kNumLowLenSymbols); + } + for (; i < numSymbols; i++) + { + prices[st + i] = b1 + _highCoder.GetPrice(i - Base.kNumLowLenSymbols - Base.kNumMidLenSymbols); + } + } + }; + + private const UInt32 kNumLenSpecSymbols = Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; + + private class LenPriceTableEncoder : LenEncoder + { + private readonly UInt32[] _prices = new UInt32[Base.kNumLenSymbols << Base.kNumPosStatesBitsEncodingMax]; + private UInt32 _tableSize; + private readonly UInt32[] _counters = new UInt32[Base.kNumPosStatesEncodingMax]; + + public void SetTableSize(UInt32 tableSize) { _tableSize = tableSize; } + + public UInt32 GetPrice(UInt32 symbol, UInt32 posState) + { + return _prices[(posState * Base.kNumLenSymbols) + symbol]; + } + + private void UpdateTable(UInt32 posState) + { + SetPrices(posState, _tableSize, _prices, posState * Base.kNumLenSymbols); + _counters[posState] = _tableSize; + } + + public void UpdateTables(UInt32 numPosStates) + { + for (UInt32 posState = 0; posState < numPosStates; posState++) + { + UpdateTable(posState); + } + } + + public new void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) + { + base.Encode(rangeEncoder, symbol, posState); + if (--_counters[posState] == 0) + { + UpdateTable(posState); + } + } + } + + private const UInt32 kNumOpts = 1 << 12; + private class Optimal + { + public Base.State State; + + public bool Prev1IsChar; + public bool Prev2; + + public UInt32 PosPrev2; + public UInt32 BackPrev2; + + public UInt32 Price; + public UInt32 PosPrev; + public UInt32 BackPrev; + + public UInt32 Backs0; + public UInt32 Backs1; + public UInt32 Backs2; + public UInt32 Backs3; + + public void MakeAsChar() { BackPrev = 0xFFFFFFFF; Prev1IsChar = false; } + public void MakeAsShortRep() { BackPrev = 0; Prev1IsChar = false; } + public bool IsShortRep() { return BackPrev == 0; } + }; + private readonly Optimal[] _optimum = new Optimal[kNumOpts]; + private LZ.IMatchFinder _matchFinder = null; + private readonly RangeCoder.Encoder _rangeEncoder = new(); + + private readonly BitEncoder[] _isMatch = new BitEncoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + private readonly BitEncoder[] _isRep = new BitEncoder[Base.kNumStates]; + private readonly BitEncoder[] _isRepG0 = new BitEncoder[Base.kNumStates]; + private readonly BitEncoder[] _isRepG1 = new BitEncoder[Base.kNumStates]; + private readonly BitEncoder[] _isRepG2 = new BitEncoder[Base.kNumStates]; + private readonly BitEncoder[] _isRep0Long = new BitEncoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + + private readonly BitTreeEncoder[] _posSlotEncoder = new BitTreeEncoder[Base.kNumLenToPosStates]; + + private readonly BitEncoder[] _posEncoders = new BitEncoder[Base.kNumFullDistances - Base.kEndPosModelIndex]; + private readonly BitTreeEncoder _posAlignEncoder = new(Base.kNumAlignBits); + + private readonly LenPriceTableEncoder _lenEncoder = new(); + private readonly LenPriceTableEncoder _repMatchLenEncoder = new(); + + private readonly LiteralEncoder _literalEncoder = new(); + + private readonly UInt32[] _matchDistances = new UInt32[(Base.kMatchMaxLen * 2) + 2]; + + private UInt32 _numFastBytes = kNumFastBytesDefault; + private UInt32 _longestMatchLength; + private UInt32 _numDistancePairs; + + private UInt32 _additionalOffset; + + private UInt32 _optimumEndIndex; + private UInt32 _optimumCurrentIndex; + + private bool _longestMatchWasFound; + + private readonly UInt32[] _posSlotPrices = new UInt32[1 << (Base.kNumPosSlotBits + Base.kNumLenToPosStatesBits)]; + private readonly UInt32[] _distancesPrices = new UInt32[Base.kNumFullDistances << Base.kNumLenToPosStatesBits]; + private readonly UInt32[] _alignPrices = new UInt32[Base.kAlignTableSize]; + private UInt32 _alignPriceCount; + + private UInt32 _distTableSize = kDefaultDictionaryLogSize * 2; + + private int _posStateBits = 2; + private UInt32 _posStateMask = 4 - 1; + private int _numLiteralPosStateBits = 0; + private int _numLiteralContextBits = 3; + + private UInt32 _dictionarySize = 1 << kDefaultDictionaryLogSize; + private UInt32 _dictionarySizePrev = 0xFFFFFFFF; + private UInt32 _numFastBytesPrev = 0xFFFFFFFF; + + private Int64 nowPos64; + private bool _finished; + private System.IO.Stream _inStream; + + private EMatchFinderType _matchFinderType = EMatchFinderType.BT4; + private bool _writeEndMark = false; + + private bool _needReleaseMFStream; + + private void Create() + { + if (_matchFinder == null) + { + LZ.BinTree bt = new(); + int numHashBytes = 4; + if (_matchFinderType == EMatchFinderType.BT2) + { + numHashBytes = 2; + } + + bt.SetType(numHashBytes); + _matchFinder = bt; + } + _literalEncoder.Create(_numLiteralPosStateBits, _numLiteralContextBits); + + if (_dictionarySize == _dictionarySizePrev && _numFastBytesPrev == _numFastBytes) + { + return; + } + + _matchFinder.Create(_dictionarySize, kNumOpts, _numFastBytes, Base.kMatchMaxLen + 1); + _dictionarySizePrev = _dictionarySize; + _numFastBytesPrev = _numFastBytes; + } + + public Encoder() + { + for (int i = 0; i < kNumOpts; i++) + { + _optimum[i] = new Optimal(); + } + + for (int i = 0; i < Base.kNumLenToPosStates; i++) + { + _posSlotEncoder[i] = new BitTreeEncoder(Base.kNumPosSlotBits); + } + } + + private void SetWriteEndMarkerMode(bool writeEndMarker) + { + _writeEndMark = writeEndMarker; + } + + private void Init() + { + BaseInit(); + _rangeEncoder.Init(); + + uint i; + for (i = 0; i < Base.kNumStates; i++) + { + for (uint j = 0; j <= _posStateMask; j++) + { + uint complexState = (i << Base.kNumPosStatesBitsMax) + j; + _isMatch[complexState].Init(); + _isRep0Long[complexState].Init(); + } + _isRep[i].Init(); + _isRepG0[i].Init(); + _isRepG1[i].Init(); + _isRepG2[i].Init(); + } + _literalEncoder.Init(); + for (i = 0; i < Base.kNumLenToPosStates; i++) + { + _posSlotEncoder[i].Init(); + } + + for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++) + { + _posEncoders[i].Init(); + } + + _lenEncoder.Init((UInt32)1 << _posStateBits); + _repMatchLenEncoder.Init((UInt32)1 << _posStateBits); + + _posAlignEncoder.Init(); + + _longestMatchWasFound = false; + _optimumEndIndex = 0; + _optimumCurrentIndex = 0; + _additionalOffset = 0; + } + + private void ReadMatchDistances(out UInt32 lenRes, out UInt32 numDistancePairs) + { + lenRes = 0; + numDistancePairs = _matchFinder.GetMatches(_matchDistances); + if (numDistancePairs > 0) + { + lenRes = _matchDistances[numDistancePairs - 2]; + if (lenRes == _numFastBytes) + { + lenRes += _matchFinder.GetMatchLen((int)lenRes - 1, _matchDistances[numDistancePairs - 1], + Base.kMatchMaxLen - lenRes); + } + } + _additionalOffset++; + } + + private void MovePos(UInt32 num) + { + if (num > 0) + { + _matchFinder.Skip(num); + _additionalOffset += num; + } + } + + private UInt32 GetRepLen1Price(Base.State state, UInt32 posState) + { + return _isRepG0[state.Index].GetPrice0() + + _isRep0Long[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0(); + } + + private UInt32 GetPureRepPrice(UInt32 repIndex, Base.State state, UInt32 posState) + { + UInt32 price; + if (repIndex == 0) + { + price = _isRepG0[state.Index].GetPrice0(); + price += _isRep0Long[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); + } + else + { + price = _isRepG0[state.Index].GetPrice1(); + if (repIndex == 1) + { + price += _isRepG1[state.Index].GetPrice0(); + } + else + { + price += _isRepG1[state.Index].GetPrice1(); + price += _isRepG2[state.Index].GetPrice(repIndex - 2); + } + } + return price; + } + + private UInt32 GetRepPrice(UInt32 repIndex, UInt32 len, Base.State state, UInt32 posState) + { + UInt32 price = _repMatchLenEncoder.GetPrice(len - Base.kMatchMinLen, posState); + return price + GetPureRepPrice(repIndex, state, posState); + } + + private UInt32 GetPosLenPrice(UInt32 pos, UInt32 len, UInt32 posState) + { + UInt32 price; + UInt32 lenToPosState = Base.GetLenToPosState(len); + price = pos < Base.kNumFullDistances + ? _distancesPrices[(lenToPosState * Base.kNumFullDistances) + pos] + : _posSlotPrices[(lenToPosState << Base.kNumPosSlotBits) + GetPosSlot2(pos)] + + _alignPrices[pos & Base.kAlignMask]; + + return price + _lenEncoder.GetPrice(len - Base.kMatchMinLen, posState); + } + + private UInt32 Backward(out UInt32 backRes, UInt32 cur) + { + _optimumEndIndex = cur; + UInt32 posMem = _optimum[cur].PosPrev; + UInt32 backMem = _optimum[cur].BackPrev; + do + { + if (_optimum[cur].Prev1IsChar) + { + _optimum[posMem].MakeAsChar(); + _optimum[posMem].PosPrev = posMem - 1; + if (_optimum[cur].Prev2) + { + _optimum[posMem - 1].Prev1IsChar = false; + _optimum[posMem - 1].PosPrev = _optimum[cur].PosPrev2; + _optimum[posMem - 1].BackPrev = _optimum[cur].BackPrev2; + } + } + UInt32 posPrev = posMem; + UInt32 backCur = backMem; + + backMem = _optimum[posPrev].BackPrev; + posMem = _optimum[posPrev].PosPrev; + + _optimum[posPrev].BackPrev = backCur; + _optimum[posPrev].PosPrev = cur; + cur = posPrev; + } + while (cur > 0); + backRes = _optimum[0].BackPrev; + _optimumCurrentIndex = _optimum[0].PosPrev; + return _optimumCurrentIndex; + } + + private readonly UInt32[] reps = new UInt32[Base.kNumRepDistances]; + private readonly UInt32[] repLens = new UInt32[Base.kNumRepDistances]; + + private UInt32 GetOptimum(UInt32 position, out UInt32 backRes) + { + if (_optimumEndIndex != _optimumCurrentIndex) + { + UInt32 lenRes = _optimum[_optimumCurrentIndex].PosPrev - _optimumCurrentIndex; + backRes = _optimum[_optimumCurrentIndex].BackPrev; + _optimumCurrentIndex = _optimum[_optimumCurrentIndex].PosPrev; + return lenRes; + } + _optimumCurrentIndex = _optimumEndIndex = 0; + + UInt32 lenMain, numDistancePairs; + if (!_longestMatchWasFound) + { + ReadMatchDistances(out lenMain, out numDistancePairs); + } + else + { + lenMain = _longestMatchLength; + numDistancePairs = _numDistancePairs; + _longestMatchWasFound = false; + } + + UInt32 numAvailableBytes = _matchFinder.GetNumAvailableBytes() + 1; + if (numAvailableBytes < 2) + { + backRes = 0xFFFFFFFF; + return 1; + } + if (numAvailableBytes > Base.kMatchMaxLen) + { + numAvailableBytes = Base.kMatchMaxLen; + } + + UInt32 repMaxIndex = 0; + UInt32 i; + for (i = 0; i < Base.kNumRepDistances; i++) + { + reps[i] = _repDistances[i]; + repLens[i] = _matchFinder.GetMatchLen(0 - 1, reps[i], Base.kMatchMaxLen); + if (repLens[i] > repLens[repMaxIndex]) + { + repMaxIndex = i; + } + } + if (repLens[repMaxIndex] >= _numFastBytes) + { + backRes = repMaxIndex; + UInt32 lenRes = repLens[repMaxIndex]; + MovePos(lenRes - 1); + return lenRes; + } + + if (lenMain >= _numFastBytes) + { + backRes = _matchDistances[numDistancePairs - 1] + Base.kNumRepDistances; + MovePos(lenMain - 1); + return lenMain; + } + + Byte currentByte = _matchFinder.GetIndexByte(0 - 1); + Byte matchByte = _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - 1)); + + if (lenMain < 2 && currentByte != matchByte && repLens[repMaxIndex] < 2) + { + backRes = 0xFFFFFFFF; + return 1; + } + + _optimum[0].State = _state; + + UInt32 posState = position & _posStateMask; + + _optimum[1].Price = _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0() + + _literalEncoder.GetSubCoder(position, _previousByte).GetPrice(!_state.IsCharState(), matchByte, currentByte); + _optimum[1].MakeAsChar(); + + UInt32 matchPrice = _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); + UInt32 repMatchPrice = matchPrice + _isRep[_state.Index].GetPrice1(); + + if (matchByte == currentByte) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(_state, posState); + if (shortRepPrice < _optimum[1].Price) + { + _optimum[1].Price = shortRepPrice; + _optimum[1].MakeAsShortRep(); + } + } + + UInt32 lenEnd = (lenMain >= repLens[repMaxIndex]) ? lenMain : repLens[repMaxIndex]; + + if (lenEnd < 2) + { + backRes = _optimum[1].BackPrev; + return 1; + } + + _optimum[1].PosPrev = 0; + + _optimum[0].Backs0 = reps[0]; + _optimum[0].Backs1 = reps[1]; + _optimum[0].Backs2 = reps[2]; + _optimum[0].Backs3 = reps[3]; + + UInt32 len = lenEnd; + do + { + _optimum[len--].Price = kIfinityPrice; + } + while (len >= 2); + + for (i = 0; i < Base.kNumRepDistances; i++) + { + UInt32 repLen = repLens[i]; + if (repLen < 2) + { + continue; + } + + UInt32 price = repMatchPrice + GetPureRepPrice(i, _state, posState); + do + { + UInt32 curAndLenPrice = price + _repMatchLenEncoder.GetPrice(repLen - 2, posState); + Optimal optimum = _optimum[repLen]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = 0; + optimum.BackPrev = i; + optimum.Prev1IsChar = false; + } + } + while (--repLen >= 2); + } + + UInt32 normalMatchPrice = matchPrice + _isRep[_state.Index].GetPrice0(); + + len = (repLens[0] >= 2) ? repLens[0] + 1 : 2; + if (len <= lenMain) + { + UInt32 offs = 0; + while (len > _matchDistances[offs]) + { + offs += 2; + } + + for (; ; len++) + { + UInt32 distance = _matchDistances[offs + 1]; + UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(distance, len, posState); + Optimal optimum = _optimum[len]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = 0; + optimum.BackPrev = distance + Base.kNumRepDistances; + optimum.Prev1IsChar = false; + } + if (len == _matchDistances[offs]) + { + offs += 2; + if (offs == numDistancePairs) + { + break; + } + } + } + } + + UInt32 cur = 0; + + while (true) + { + cur++; + if (cur == lenEnd) + { + return Backward(out backRes, cur); + } + + ReadMatchDistances(out uint newLen, out numDistancePairs); + if (newLen >= _numFastBytes) + { + _numDistancePairs = numDistancePairs; + _longestMatchLength = newLen; + _longestMatchWasFound = true; + return Backward(out backRes, cur); + } + position++; + UInt32 posPrev = _optimum[cur].PosPrev; + Base.State state; + if (_optimum[cur].Prev1IsChar) + { + posPrev--; + if (_optimum[cur].Prev2) + { + state = _optimum[_optimum[cur].PosPrev2].State; + if (_optimum[cur].BackPrev2 < Base.kNumRepDistances) + { + state.UpdateRep(); + } + else + { + state.UpdateMatch(); + } + } + else + { + state = _optimum[posPrev].State; + } + + state.UpdateChar(); + } + else + { + state = _optimum[posPrev].State; + } + + if (posPrev == cur - 1) + { + if (_optimum[cur].IsShortRep()) + { + state.UpdateShortRep(); + } + else + { + state.UpdateChar(); + } + } + else + { + UInt32 pos; + if (_optimum[cur].Prev1IsChar && _optimum[cur].Prev2) + { + posPrev = _optimum[cur].PosPrev2; + pos = _optimum[cur].BackPrev2; + state.UpdateRep(); + } + else + { + pos = _optimum[cur].BackPrev; + if (pos < Base.kNumRepDistances) + { + state.UpdateRep(); + } + else + { + state.UpdateMatch(); + } + } + Optimal opt = _optimum[posPrev]; + if (pos < Base.kNumRepDistances) + { + if (pos == 0) + { + reps[0] = opt.Backs0; + reps[1] = opt.Backs1; + reps[2] = opt.Backs2; + reps[3] = opt.Backs3; + } + else if (pos == 1) + { + reps[0] = opt.Backs1; + reps[1] = opt.Backs0; + reps[2] = opt.Backs2; + reps[3] = opt.Backs3; + } + else if (pos == 2) + { + reps[0] = opt.Backs2; + reps[1] = opt.Backs0; + reps[2] = opt.Backs1; + reps[3] = opt.Backs3; + } + else + { + reps[0] = opt.Backs3; + reps[1] = opt.Backs0; + reps[2] = opt.Backs1; + reps[3] = opt.Backs2; + } + } + else + { + reps[0] = pos - Base.kNumRepDistances; + reps[1] = opt.Backs0; + reps[2] = opt.Backs1; + reps[3] = opt.Backs2; + } + } + _optimum[cur].State = state; + _optimum[cur].Backs0 = reps[0]; + _optimum[cur].Backs1 = reps[1]; + _optimum[cur].Backs2 = reps[2]; + _optimum[cur].Backs3 = reps[3]; + UInt32 curPrice = _optimum[cur].Price; + + currentByte = _matchFinder.GetIndexByte(0 - 1); + matchByte = _matchFinder.GetIndexByte((Int32)(0 - reps[0] - 1 - 1)); + + posState = position & _posStateMask; + + UInt32 curAnd1Price = curPrice + + _isMatch[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0() + + _literalEncoder.GetSubCoder(position, _matchFinder.GetIndexByte(0 - 2)). + GetPrice(!state.IsCharState(), matchByte, currentByte); + + Optimal nextOptimum = _optimum[cur + 1]; + + bool nextIsChar = false; + if (curAnd1Price < nextOptimum.Price) + { + nextOptimum.Price = curAnd1Price; + nextOptimum.PosPrev = cur; + nextOptimum.MakeAsChar(); + nextIsChar = true; + } + + matchPrice = curPrice + _isMatch[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); + repMatchPrice = matchPrice + _isRep[state.Index].GetPrice1(); + + if (matchByte == currentByte && + !(nextOptimum.PosPrev < cur && nextOptimum.BackPrev == 0)) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(state, posState); + if (shortRepPrice <= nextOptimum.Price) + { + nextOptimum.Price = shortRepPrice; + nextOptimum.PosPrev = cur; + nextOptimum.MakeAsShortRep(); + nextIsChar = true; + } + } + + UInt32 numAvailableBytesFull = _matchFinder.GetNumAvailableBytes() + 1; + numAvailableBytesFull = Math.Min(kNumOpts - 1 - cur, numAvailableBytesFull); + numAvailableBytes = numAvailableBytesFull; + + if (numAvailableBytes < 2) + { + continue; + } + + if (numAvailableBytes > _numFastBytes) + { + numAvailableBytes = _numFastBytes; + } + + if (!nextIsChar && matchByte != currentByte) + { + // try Literal + rep0 + UInt32 t = Math.Min(numAvailableBytesFull - 1, _numFastBytes); + UInt32 lenTest2 = _matchFinder.GetMatchLen(0, reps[0], t); + if (lenTest2 >= 2) + { + Base.State state2 = state; + state2.UpdateChar(); + UInt32 posStateNext = (position + 1) & _posStateMask; + UInt32 nextRepMatchPrice = curAnd1Price + + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1() + + _isRep[state2.Index].GetPrice1(); + { + UInt32 offset = cur + 1 + lenTest2; + while (lenEnd < offset) + { + _optimum[++lenEnd].Price = kIfinityPrice; + } + + UInt32 curAndLenPrice = nextRepMatchPrice + GetRepPrice( + 0, lenTest2, state2, posStateNext); + Optimal optimum = _optimum[offset]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur + 1; + optimum.BackPrev = 0; + optimum.Prev1IsChar = true; + optimum.Prev2 = false; + } + } + } + } + + UInt32 startLen = 2; // speed optimization + + for (UInt32 repIndex = 0; repIndex < Base.kNumRepDistances; repIndex++) + { + UInt32 lenTest = _matchFinder.GetMatchLen(0 - 1, reps[repIndex], numAvailableBytes); + if (lenTest < 2) + { + continue; + } + + UInt32 lenTestTemp = lenTest; + do + { + while (lenEnd < cur + lenTest) + { + _optimum[++lenEnd].Price = kIfinityPrice; + } + + UInt32 curAndLenPrice = repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState); + Optimal optimum = _optimum[cur + lenTest]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur; + optimum.BackPrev = repIndex; + optimum.Prev1IsChar = false; + } + } + while (--lenTest >= 2); + lenTest = lenTestTemp; + + if (repIndex == 0) + { + startLen = lenTest + 1; + } + + // if (_maxMode) + if (lenTest < numAvailableBytesFull) + { + UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); + UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, reps[repIndex], t); + if (lenTest2 >= 2) + { + Base.State state2 = state; + state2.UpdateRep(); + UInt32 posStateNext = (position + lenTest) & _posStateMask; + UInt32 curAndLenCharPrice = + repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState) + + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice0() + + _literalEncoder.GetSubCoder(position + lenTest, + _matchFinder.GetIndexByte((Int32)lenTest - 1 - 1)).GetPrice(true, + _matchFinder.GetIndexByte((Int32)lenTest - 1 - (Int32)(reps[repIndex] + 1)), + _matchFinder.GetIndexByte((Int32)lenTest - 1)); + state2.UpdateChar(); + posStateNext = (position + lenTest + 1) & _posStateMask; + UInt32 nextMatchPrice = curAndLenCharPrice + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1(); + UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2.Index].GetPrice1(); + + // for(; lenTest2 >= 2; lenTest2--) + { + UInt32 offset = lenTest + 1 + lenTest2; + while (lenEnd < cur + offset) + { + _optimum[++lenEnd].Price = kIfinityPrice; + } + + UInt32 curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); + Optimal optimum = _optimum[cur + offset]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur + lenTest + 1; + optimum.BackPrev = 0; + optimum.Prev1IsChar = true; + optimum.Prev2 = true; + optimum.PosPrev2 = cur; + optimum.BackPrev2 = repIndex; + } + } + } + } + } + + if (newLen > numAvailableBytes) + { + newLen = numAvailableBytes; + for (numDistancePairs = 0; newLen > _matchDistances[numDistancePairs]; numDistancePairs += 2) + { + } + + _matchDistances[numDistancePairs] = newLen; + numDistancePairs += 2; + } + if (newLen >= startLen) + { + normalMatchPrice = matchPrice + _isRep[state.Index].GetPrice0(); + while (lenEnd < cur + newLen) + { + _optimum[++lenEnd].Price = kIfinityPrice; + } + + UInt32 offs = 0; + while (startLen > _matchDistances[offs]) + { + offs += 2; + } + + for (UInt32 lenTest = startLen; ; lenTest++) + { + UInt32 curBack = _matchDistances[offs + 1]; + UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(curBack, lenTest, posState); + Optimal optimum = _optimum[cur + lenTest]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur; + optimum.BackPrev = curBack + Base.kNumRepDistances; + optimum.Prev1IsChar = false; + } + + if (lenTest == _matchDistances[offs]) + { + if (lenTest < numAvailableBytesFull) + { + UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); + UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, curBack, t); + if (lenTest2 >= 2) + { + Base.State state2 = state; + state2.UpdateMatch(); + UInt32 posStateNext = (position + lenTest) & _posStateMask; + UInt32 curAndLenCharPrice = curAndLenPrice + + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice0() + + _literalEncoder.GetSubCoder(position + lenTest, + _matchFinder.GetIndexByte((Int32)lenTest - 1 - 1)). + GetPrice(true, + _matchFinder.GetIndexByte((Int32)lenTest - (Int32)(curBack + 1) - 1), + _matchFinder.GetIndexByte((Int32)lenTest - 1)); + state2.UpdateChar(); + posStateNext = (position + lenTest + 1) & _posStateMask; + UInt32 nextMatchPrice = curAndLenCharPrice + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1(); + UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2.Index].GetPrice1(); + + UInt32 offset = lenTest + 1 + lenTest2; + while (lenEnd < cur + offset) + { + _optimum[++lenEnd].Price = kIfinityPrice; + } + + curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); + optimum = _optimum[cur + offset]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur + lenTest + 1; + optimum.BackPrev = 0; + optimum.Prev1IsChar = true; + optimum.Prev2 = true; + optimum.PosPrev2 = cur; + optimum.BackPrev2 = curBack + Base.kNumRepDistances; + } + } + } + offs += 2; + if (offs == numDistancePairs) + { + break; + } + } + } + } + } + } + + private bool ChangePair(UInt32 smallDist, UInt32 bigDist) + { + const int kDif = 7; + return smallDist < ((UInt32)1 << (32 - kDif)) && bigDist >= (smallDist << kDif); + } + + private void WriteEndMarker(UInt32 posState) + { + if (!_writeEndMark) + { + return; + } + + _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].Encode(_rangeEncoder, 1); + _isRep[_state.Index].Encode(_rangeEncoder, 0); + _state.UpdateMatch(); + const UInt32 len = Base.kMatchMinLen; + _lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); + const UInt32 posSlot = (1 << Base.kNumPosSlotBits) - 1; + UInt32 lenToPosState = Base.GetLenToPosState(len); + _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); + const int footerBits = 30; + const UInt32 posReduced = (((UInt32)1) << footerBits) - 1; + _rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits); + _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask); + } + + private void Flush(UInt32 nowPos) + { + ReleaseMFStream(); + WriteEndMarker(nowPos & _posStateMask); + _rangeEncoder.FlushData(); + _rangeEncoder.FlushStream(); + } + + public void CodeOneBlock(out Int64 inSize, out Int64 outSize, out bool finished) + { + inSize = 0; + outSize = 0; + finished = true; + + if (_inStream != null) + { + _matchFinder.SetStream(_inStream); + _matchFinder.Init(); + _needReleaseMFStream = true; + _inStream = null; + if (_trainSize > 0) + { + _matchFinder.Skip(_trainSize); + } + } + + if (_finished) + { + return; + } + + _finished = true; + + Int64 progressPosValuePrev = nowPos64; + if (nowPos64 == 0) + { + if (_matchFinder.GetNumAvailableBytes() == 0) + { + Flush((UInt32)nowPos64); + return; + } + // it's not used + ReadMatchDistances(out uint len, out uint numDistancePairs); + UInt32 posState = (UInt32)nowPos64 & _posStateMask; + _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].Encode(_rangeEncoder, 0); + _state.UpdateChar(); + Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); + _literalEncoder.GetSubCoder((UInt32)nowPos64, _previousByte).Encode(_rangeEncoder, curByte); + _previousByte = curByte; + _additionalOffset--; + nowPos64++; + } + if (_matchFinder.GetNumAvailableBytes() == 0) + { + Flush((UInt32)nowPos64); + return; + } + while (true) + { + UInt32 len = GetOptimum((UInt32)nowPos64, out uint pos); + + UInt32 posState = ((UInt32)nowPos64) & _posStateMask; + UInt32 complexState = (_state.Index << Base.kNumPosStatesBitsMax) + posState; + if (len == 1 && pos == 0xFFFFFFFF) + { + _isMatch[complexState].Encode(_rangeEncoder, 0); + Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); + LiteralEncoder.Encoder2 subCoder = _literalEncoder.GetSubCoder((UInt32)nowPos64, _previousByte); + if (!_state.IsCharState()) + { + Byte matchByte = _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - _additionalOffset)); + subCoder.EncodeMatched(_rangeEncoder, matchByte, curByte); + } + else + { + subCoder.Encode(_rangeEncoder, curByte); + } + + _previousByte = curByte; + _state.UpdateChar(); + } + else + { + _isMatch[complexState].Encode(_rangeEncoder, 1); + if (pos < Base.kNumRepDistances) + { + _isRep[_state.Index].Encode(_rangeEncoder, 1); + if (pos == 0) + { + _isRepG0[_state.Index].Encode(_rangeEncoder, 0); + if (len == 1) + { + _isRep0Long[complexState].Encode(_rangeEncoder, 0); + } + else + { + _isRep0Long[complexState].Encode(_rangeEncoder, 1); + } + } + else + { + _isRepG0[_state.Index].Encode(_rangeEncoder, 1); + if (pos == 1) + { + _isRepG1[_state.Index].Encode(_rangeEncoder, 0); + } + else + { + _isRepG1[_state.Index].Encode(_rangeEncoder, 1); + _isRepG2[_state.Index].Encode(_rangeEncoder, pos - 2); + } + } + if (len == 1) + { + _state.UpdateShortRep(); + } + else + { + _repMatchLenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); + _state.UpdateRep(); + } + UInt32 distance = _repDistances[pos]; + if (pos != 0) + { + for (UInt32 i = pos; i >= 1; i--) + { + _repDistances[i] = _repDistances[i - 1]; + } + + _repDistances[0] = distance; + } + } + else + { + _isRep[_state.Index].Encode(_rangeEncoder, 0); + _state.UpdateMatch(); + _lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); + pos -= Base.kNumRepDistances; + UInt32 posSlot = GetPosSlot(pos); + UInt32 lenToPosState = Base.GetLenToPosState(len); + _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); + + if (posSlot >= Base.kStartPosModelIndex) + { + int footerBits = (int)((posSlot >> 1) - 1); + UInt32 baseVal = (2 | (posSlot & 1)) << footerBits; + UInt32 posReduced = pos - baseVal; + + if (posSlot < Base.kEndPosModelIndex) + { + BitTreeEncoder.ReverseEncode(_posEncoders, + baseVal - posSlot - 1, _rangeEncoder, footerBits, posReduced); + } + else + { + _rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits); + _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask); + _alignPriceCount++; + } + } + UInt32 distance = pos; + for (UInt32 i = Base.kNumRepDistances - 1; i >= 1; i--) + { + _repDistances[i] = _repDistances[i - 1]; + } + + _repDistances[0] = distance; + _matchPriceCount++; + } + _previousByte = _matchFinder.GetIndexByte((Int32)(len - 1 - _additionalOffset)); + } + _additionalOffset -= len; + nowPos64 += len; + if (_additionalOffset == 0) + { + // if (!_fastMode) + if (_matchPriceCount >= (1 << 7)) + { + FillDistancesPrices(); + } + + if (_alignPriceCount >= Base.kAlignTableSize) + { + FillAlignPrices(); + } + + inSize = nowPos64; + outSize = _rangeEncoder.GetProcessedSizeAdd(); + if (_matchFinder.GetNumAvailableBytes() == 0) + { + Flush((UInt32)nowPos64); + return; + } + + if (nowPos64 - progressPosValuePrev >= (1 << 12)) + { + _finished = false; + finished = false; + return; + } + } + } + } + + private void ReleaseMFStream() + { + if (_matchFinder != null && _needReleaseMFStream) + { + _matchFinder.ReleaseStream(); + _needReleaseMFStream = false; + } + } + + private void SetOutStream(System.IO.Stream outStream) { _rangeEncoder.SetStream(outStream); } + private void ReleaseOutStream() { _rangeEncoder.ReleaseStream(); } + + private void ReleaseStreams() + { + ReleaseMFStream(); + ReleaseOutStream(); + } + + private void SetStreams(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize) + { + _inStream = inStream; + _finished = false; + Create(); + SetOutStream(outStream); + Init(); + + // if (!_fastMode) + { + FillDistancesPrices(); + FillAlignPrices(); + } + + _lenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen); + _lenEncoder.UpdateTables((UInt32)1 << _posStateBits); + _repMatchLenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen); + _repMatchLenEncoder.UpdateTables((UInt32)1 << _posStateBits); + + nowPos64 = 0; + } + + public void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken? token = null) + { + _needReleaseMFStream = false; + try + { + SetStreams(inStream, outStream, inSize, outSize); + while (true) + { + token?.ThrowIfCancellationRequested(); + CodeOneBlock(out long processedInSize, out long processedOutSize, out bool finished); + if (finished) + { + return; + } + + progress?.SetProgress(processedInSize, processedOutSize); + } + } + finally + { + ReleaseStreams(); + } + } + + private const int kPropSize = 5; + private readonly Byte[] properties = new Byte[kPropSize]; + + public void WriteCoderProperties(System.IO.Stream outStream) + { + properties[0] = (Byte)((((_posStateBits * 5) + _numLiteralPosStateBits) * 9) + _numLiteralContextBits); + for (int i = 0; i < 4; i++) + { + properties[1 + i] = (Byte)((_dictionarySize >> (8 * i)) & 0xFF); + } + + outStream.Write(properties, 0, kPropSize); + } + + private readonly UInt32[] tempPrices = new UInt32[Base.kNumFullDistances]; + private UInt32 _matchPriceCount; + + private void FillDistancesPrices() + { + for (UInt32 i = Base.kStartPosModelIndex; i < Base.kNumFullDistances; i++) + { + UInt32 posSlot = GetPosSlot(i); + int footerBits = (int)((posSlot >> 1) - 1); + UInt32 baseVal = (2 | (posSlot & 1)) << footerBits; + tempPrices[i] = BitTreeEncoder.ReverseGetPrice(_posEncoders, + baseVal - posSlot - 1, footerBits, i - baseVal); + } + + for (UInt32 lenToPosState = 0; lenToPosState < Base.kNumLenToPosStates; lenToPosState++) + { + UInt32 posSlot; + BitTreeEncoder encoder = _posSlotEncoder[lenToPosState]; + + UInt32 st = lenToPosState << Base.kNumPosSlotBits; + for (posSlot = 0; posSlot < _distTableSize; posSlot++) + { + _posSlotPrices[st + posSlot] = encoder.GetPrice(posSlot); + } + + for (posSlot = Base.kEndPosModelIndex; posSlot < _distTableSize; posSlot++) + { + _posSlotPrices[st + posSlot] += ((posSlot >> 1) - 1 - Base.kNumAlignBits) << BitEncoder.kNumBitPriceShiftBits; + } + + UInt32 st2 = lenToPosState * Base.kNumFullDistances; + UInt32 i; + for (i = 0; i < Base.kStartPosModelIndex; i++) + { + _distancesPrices[st2 + i] = _posSlotPrices[st + i]; + } + + for (; i < Base.kNumFullDistances; i++) + { + _distancesPrices[st2 + i] = _posSlotPrices[st + GetPosSlot(i)] + tempPrices[i]; + } + } + _matchPriceCount = 0; + } + + private void FillAlignPrices() + { + for (UInt32 i = 0; i < Base.kAlignTableSize; i++) + { + _alignPrices[i] = _posAlignEncoder.ReverseGetPrice(i); + } + + _alignPriceCount = 0; + } + + private static readonly string[] kMatchFinderIDs = + { + "BT2", + "BT4", + }; + + private static int FindMatchFinder(string s) + { + for (int m = 0; m < kMatchFinderIDs.Length; m++) + { + if (s == kMatchFinderIDs[m]) + { + return m; + } + } + + return -1; + } + + public void SetCoderProperties(CoderPropID[] propIDs, object[] properties) + { + for (UInt32 i = 0; i < properties.Length; i++) + { + object prop = properties[i]; + switch (propIDs[i]) + { + case CoderPropID.NumFastBytes: + { + if (!(prop is Int32)) + { + throw new InvalidParamException(); + } + + Int32 numFastBytes = (Int32)prop; + if (numFastBytes < 5 || numFastBytes > Base.kMatchMaxLen) + { + throw new InvalidParamException(); + } + + _numFastBytes = (UInt32)numFastBytes; + break; + } + case CoderPropID.Algorithm: + { + /* + if (!(prop is Int32)) + throw new InvalidParamException(); + Int32 maximize = (Int32)prop; + _fastMode = (maximize == 0); + _maxMode = (maximize >= 2); + */ + break; + } + case CoderPropID.MatchFinder: + { + if (!(prop is String)) + { + throw new InvalidParamException(); + } + + EMatchFinderType matchFinderIndexPrev = _matchFinderType; + int m = FindMatchFinder(((string)prop).ToUpper()); + if (m < 0) + { + throw new InvalidParamException(); + } + + _matchFinderType = (EMatchFinderType)m; + if (_matchFinder != null && matchFinderIndexPrev != _matchFinderType) + { + _dictionarySizePrev = 0xFFFFFFFF; + _matchFinder = null; + } + break; + } + case CoderPropID.DictionarySize: + { + const int kDicLogSizeMaxCompress = 30; + if (!(prop is Int32)) + { + throw new InvalidParamException(); + } + Int32 dictionarySize = (Int32)prop; + if (dictionarySize < (UInt32)(1 << Base.kDicLogSizeMin) || + dictionarySize > (UInt32)(1 << kDicLogSizeMaxCompress)) + { + throw new InvalidParamException(); + } + + _dictionarySize = (UInt32)dictionarySize; + int dicLogSize; + for (dicLogSize = 0; dicLogSize < (UInt32)kDicLogSizeMaxCompress; dicLogSize++) + { + if (dictionarySize <= ((UInt32)1 << dicLogSize)) + { + break; + } + } + + _distTableSize = (UInt32)dicLogSize * 2; + break; + } + case CoderPropID.PosStateBits: + { + if (!(prop is Int32)) + { + throw new InvalidParamException(); + } + + Int32 v = (Int32)prop; + if (v < 0 || v > (UInt32)Base.kNumPosStatesBitsEncodingMax) + { + throw new InvalidParamException(); + } + + _posStateBits = v; + _posStateMask = (((UInt32)1) << _posStateBits) - 1; + break; + } + case CoderPropID.LitPosBits: + { + if (!(prop is Int32)) + { + throw new InvalidParamException(); + } + + Int32 v = (Int32)prop; + if (v < 0 || v > Base.kNumLitPosStatesBitsEncodingMax) + { + throw new InvalidParamException(); + } + + _numLiteralPosStateBits = v; + break; + } + case CoderPropID.LitContextBits: + { + if (!(prop is Int32)) + { + throw new InvalidParamException(); + } + + Int32 v = (Int32)prop; + if (v < 0 || v > Base.kNumLitContextBitsMax) + { + throw new InvalidParamException(); + } + _numLiteralContextBits = v; + break; + } + case CoderPropID.EndMarker: + { + if (!(prop is Boolean)) + { + throw new InvalidParamException(); + } + + SetWriteEndMarkerMode((Boolean)prop); + break; + } + default: + throw new InvalidParamException(); + } + } + } + + private uint _trainSize = 0; + public void SetTrainSize(uint trainSize) + { + _trainSize = trainSize; + } + } +} diff --git a/7zip/Compress/RangeCoder/RangeCoder.cs b/WPinternals/7zip/Compress/RangeCoder/RangeCoder.cs similarity index 95% rename from 7zip/Compress/RangeCoder/RangeCoder.cs rename to WPinternals/7zip/Compress/RangeCoder/RangeCoder.cs index 9b5d50d..5e57e11 100644 --- a/7zip/Compress/RangeCoder/RangeCoder.cs +++ b/WPinternals/7zip/Compress/RangeCoder/RangeCoder.cs @@ -1,243 +1,243 @@ -using System; - -namespace SevenZip.Compression.RangeCoder -{ - internal class Encoder - { - public const uint kTopValue = 1 << 24; - - private System.IO.Stream Stream; - - public UInt64 Low; - public uint Range; - private uint _cacheSize; - private byte _cache; - - private long StartPosition; - - public void SetStream(System.IO.Stream stream) - { - Stream = stream; - } - - public void ReleaseStream() - { - Stream = null; - } - - public void Init() - { - StartPosition = Stream.Position; - - Low = 0; - Range = 0xFFFFFFFF; - _cacheSize = 1; - _cache = 0; - } - - public void FlushData() - { - for (int i = 0; i < 5; i++) - { - ShiftLow(); - } - } - - public void FlushStream() - { - Stream.Flush(); - } - - public void CloseStream() - { - Stream.Close(); - } - - public void Encode(uint start, uint size, uint total) - { - Low += start * (Range /= total); - Range *= size; - while (Range < kTopValue) - { - Range <<= 8; - ShiftLow(); - } - } - - public void ShiftLow() - { - if ((uint)Low < 0xFF000000 || (uint)(Low >> 32) == 1) - { - byte temp = _cache; - do - { - Stream.WriteByte((byte)(temp + (Low >> 32))); - temp = 0xFF; - } - while (--_cacheSize != 0); - _cache = (byte)(((uint)Low) >> 24); - } - _cacheSize++; - Low = ((uint)Low) << 8; - } - - public void EncodeDirectBits(uint v, int numTotalBits) - { - for (int i = numTotalBits - 1; i >= 0; i--) - { - Range >>= 1; - if (((v >> i) & 1) == 1) - { - Low += Range; - } - - if (Range < kTopValue) - { - Range <<= 8; - ShiftLow(); - } - } - } - - public void EncodeBit(uint size0, int numTotalBits, uint symbol) - { - uint newBound = (Range >> numTotalBits) * size0; - if (symbol == 0) - { - Range = newBound; - } - else - { - Low += newBound; - Range -= newBound; - } - while (Range < kTopValue) - { - Range <<= 8; - ShiftLow(); - } - } - - public long GetProcessedSizeAdd() - { - return _cacheSize + - Stream.Position - StartPosition + 4; - // (long)Stream.GetProcessedSize(); - } - } - - internal class Decoder - { - public const uint kTopValue = 1 << 24; - public uint Range; - public uint Code; - // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); - public System.IO.Stream Stream; - - public void Init(System.IO.Stream stream) - { - // Stream.Init(stream); - Stream = stream; - - Code = 0; - Range = 0xFFFFFFFF; - for (int i = 0; i < 5; i++) - { - Code = (Code << 8) | (byte)Stream.ReadByte(); - } - } - - public void ReleaseStream() - { - // Stream.ReleaseStream(); - Stream = null; - } - - public void CloseStream() - { - Stream.Close(); - } - - public void Normalize() - { - while (Range < kTopValue) - { - Code = (Code << 8) | (byte)Stream.ReadByte(); - Range <<= 8; - } - } - - public void Normalize2() - { - if (Range < kTopValue) - { - Code = (Code << 8) | (byte)Stream.ReadByte(); - Range <<= 8; - } - } - - public uint GetThreshold(uint total) - { - return Code / (Range /= total); - } - - public void Decode(uint start, uint size, uint total) - { - Code -= start * Range; - Range *= size; - Normalize(); - } - - public uint DecodeDirectBits(int numTotalBits) - { - uint range = Range; - uint code = Code; - uint result = 0; - for (int i = numTotalBits; i > 0; i--) - { - range >>= 1; - /* - result <<= 1; - if (code >= range) - { - code -= range; - result |= 1; - } - */ - uint t = (code - range) >> 31; - code -= range & (t - 1); - result = (result << 1) | (1 - t); - - if (range < kTopValue) - { - code = (code << 8) | (byte)Stream.ReadByte(); - range <<= 8; - } - } - Range = range; - Code = code; - return result; - } - - public uint DecodeBit(uint size0, int numTotalBits) - { - uint newBound = (Range >> numTotalBits) * size0; - uint symbol; - if (Code < newBound) - { - symbol = 0; - Range = newBound; - } - else - { - symbol = 1; - Code -= newBound; - Range -= newBound; - } - Normalize(); - return symbol; - } - - // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } - } -} +using System; + +namespace SevenZip.Compression.RangeCoder +{ + internal class Encoder + { + public const uint kTopValue = 1 << 24; + + private System.IO.Stream Stream; + + public UInt64 Low; + public uint Range; + private uint _cacheSize; + private byte _cache; + + private long StartPosition; + + public void SetStream(System.IO.Stream stream) + { + Stream = stream; + } + + public void ReleaseStream() + { + Stream = null; + } + + public void Init() + { + StartPosition = Stream.Position; + + Low = 0; + Range = 0xFFFFFFFF; + _cacheSize = 1; + _cache = 0; + } + + public void FlushData() + { + for (int i = 0; i < 5; i++) + { + ShiftLow(); + } + } + + public void FlushStream() + { + Stream.Flush(); + } + + public void CloseStream() + { + Stream.Close(); + } + + public void Encode(uint start, uint size, uint total) + { + Low += start * (Range /= total); + Range *= size; + while (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + + public void ShiftLow() + { + if ((uint)Low < 0xFF000000 || (uint)(Low >> 32) == 1) + { + byte temp = _cache; + do + { + Stream.WriteByte((byte)(temp + (Low >> 32))); + temp = 0xFF; + } + while (--_cacheSize != 0); + _cache = (byte)(((uint)Low) >> 24); + } + _cacheSize++; + Low = ((uint)Low) << 8; + } + + public void EncodeDirectBits(uint v, int numTotalBits) + { + for (int i = numTotalBits - 1; i >= 0; i--) + { + Range >>= 1; + if (((v >> i) & 1) == 1) + { + Low += Range; + } + + if (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + } + + public void EncodeBit(uint size0, int numTotalBits, uint symbol) + { + uint newBound = (Range >> numTotalBits) * size0; + if (symbol == 0) + { + Range = newBound; + } + else + { + Low += newBound; + Range -= newBound; + } + while (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + + public long GetProcessedSizeAdd() + { + return _cacheSize + + Stream.Position - StartPosition + 4; + // (long)Stream.GetProcessedSize(); + } + } + + internal class Decoder + { + public const uint kTopValue = 1 << 24; + public uint Range; + public uint Code; + // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); + public System.IO.Stream Stream; + + public void Init(System.IO.Stream stream) + { + // Stream.Init(stream); + Stream = stream; + + Code = 0; + Range = 0xFFFFFFFF; + for (int i = 0; i < 5; i++) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + } + } + + public void ReleaseStream() + { + // Stream.ReleaseStream(); + Stream = null; + } + + public void CloseStream() + { + Stream.Close(); + } + + public void Normalize() + { + while (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public void Normalize2() + { + if (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public uint GetThreshold(uint total) + { + return Code / (Range /= total); + } + + public void Decode(uint start, uint size, uint total) + { + Code -= start * Range; + Range *= size; + Normalize(); + } + + public uint DecodeDirectBits(int numTotalBits) + { + uint range = Range; + uint code = Code; + uint result = 0; + for (int i = numTotalBits; i > 0; i--) + { + range >>= 1; + /* + result <<= 1; + if (code >= range) + { + code -= range; + result |= 1; + } + */ + uint t = (code - range) >> 31; + code -= range & (t - 1); + result = (result << 1) | (1 - t); + + if (range < kTopValue) + { + code = (code << 8) | (byte)Stream.ReadByte(); + range <<= 8; + } + } + Range = range; + Code = code; + return result; + } + + public uint DecodeBit(uint size0, int numTotalBits) + { + uint newBound = (Range >> numTotalBits) * size0; + uint symbol; + if (Code < newBound) + { + symbol = 0; + Range = newBound; + } + else + { + symbol = 1; + Code -= newBound; + Range -= newBound; + } + Normalize(); + return symbol; + } + + // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } + } +} diff --git a/7zip/Compress/RangeCoder/RangeCoderBit.cs b/WPinternals/7zip/Compress/RangeCoder/RangeCoderBit.cs similarity index 97% rename from 7zip/Compress/RangeCoder/RangeCoderBit.cs rename to WPinternals/7zip/Compress/RangeCoder/RangeCoderBit.cs index d7162f8..aee96b6 100644 --- a/7zip/Compress/RangeCoder/RangeCoderBit.cs +++ b/WPinternals/7zip/Compress/RangeCoder/RangeCoderBit.cs @@ -1,127 +1,127 @@ -using System; - -namespace SevenZip.Compression.RangeCoder -{ - internal struct BitEncoder - { - public const int kNumBitModelTotalBits = 11; - public const uint kBitModelTotal = 1 << kNumBitModelTotalBits; - private const int kNumMoveBits = 5; - private const int kNumMoveReducingBits = 2; - public const int kNumBitPriceShiftBits = 6; - - private uint Prob; - - public void Init() { Prob = kBitModelTotal >> 1; } - - public void UpdateModel(uint symbol) - { - if (symbol == 0) - { - Prob += (kBitModelTotal - Prob) >> kNumMoveBits; - } - else - { - Prob -= Prob >> kNumMoveBits; - } - } - - public void Encode(Encoder encoder, uint symbol) - { - // encoder.EncodeBit(Prob, kNumBitModelTotalBits, symbol); - // UpdateModel(symbol); - uint newBound = (encoder.Range >> kNumBitModelTotalBits) * Prob; - if (symbol == 0) - { - encoder.Range = newBound; - Prob += (kBitModelTotal - Prob) >> kNumMoveBits; - } - else - { - encoder.Low += newBound; - encoder.Range -= newBound; - Prob -= Prob >> kNumMoveBits; - } - if (encoder.Range < Encoder.kTopValue) - { - encoder.Range <<= 8; - encoder.ShiftLow(); - } - } - - private static readonly UInt32[] ProbPrices = new UInt32[kBitModelTotal >> kNumMoveReducingBits]; - - static BitEncoder() - { - const int kNumBits = kNumBitModelTotalBits - kNumMoveReducingBits; - for (int i = kNumBits - 1; i >= 0; i--) - { - UInt32 start = (UInt32)1 << (kNumBits - i - 1); - UInt32 end = (UInt32)1 << (kNumBits - i); - for (UInt32 j = start; j < end; j++) - { - ProbPrices[j] = ((UInt32)i << kNumBitPriceShiftBits) + - (((end - j) << kNumBitPriceShiftBits) >> (kNumBits - i - 1)); - } - } - } - - public uint GetPrice(uint symbol) - { - return ProbPrices[(((Prob - symbol) ^ (-(int)symbol)) & (kBitModelTotal - 1)) >> kNumMoveReducingBits]; - } - public uint GetPrice0() { return ProbPrices[Prob >> kNumMoveReducingBits]; } - public uint GetPrice1() { return ProbPrices[(kBitModelTotal - Prob) >> kNumMoveReducingBits]; } - } - - internal struct BitDecoder - { - public const int kNumBitModelTotalBits = 11; - public const uint kBitModelTotal = 1 << kNumBitModelTotalBits; - private const int kNumMoveBits = 5; - - private uint Prob; - - public void UpdateModel(int numMoveBits, uint symbol) - { - if (symbol == 0) - { - Prob += (kBitModelTotal - Prob) >> numMoveBits; - } - else - { - Prob -= Prob >> numMoveBits; - } - } - - public void Init() { Prob = kBitModelTotal >> 1; } - - public uint Decode(Decoder rangeDecoder) - { - uint newBound = (rangeDecoder.Range >> kNumBitModelTotalBits) * Prob; - if (rangeDecoder.Code < newBound) - { - rangeDecoder.Range = newBound; - Prob += (kBitModelTotal - Prob) >> kNumMoveBits; - if (rangeDecoder.Range < Decoder.kTopValue) - { - rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); - rangeDecoder.Range <<= 8; - } - return 0; - } - else - { - rangeDecoder.Range -= newBound; - rangeDecoder.Code -= newBound; - Prob -= Prob >> kNumMoveBits; - if (rangeDecoder.Range < Decoder.kTopValue) - { - rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); - rangeDecoder.Range <<= 8; - } - return 1; - } - } - } -} +using System; + +namespace SevenZip.Compression.RangeCoder +{ + internal struct BitEncoder + { + public const int kNumBitModelTotalBits = 11; + public const uint kBitModelTotal = 1 << kNumBitModelTotalBits; + private const int kNumMoveBits = 5; + private const int kNumMoveReducingBits = 2; + public const int kNumBitPriceShiftBits = 6; + + private uint Prob; + + public void Init() { Prob = kBitModelTotal >> 1; } + + public void UpdateModel(uint symbol) + { + if (symbol == 0) + { + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + } + else + { + Prob -= Prob >> kNumMoveBits; + } + } + + public void Encode(Encoder encoder, uint symbol) + { + // encoder.EncodeBit(Prob, kNumBitModelTotalBits, symbol); + // UpdateModel(symbol); + uint newBound = (encoder.Range >> kNumBitModelTotalBits) * Prob; + if (symbol == 0) + { + encoder.Range = newBound; + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + } + else + { + encoder.Low += newBound; + encoder.Range -= newBound; + Prob -= Prob >> kNumMoveBits; + } + if (encoder.Range < Encoder.kTopValue) + { + encoder.Range <<= 8; + encoder.ShiftLow(); + } + } + + private static readonly UInt32[] ProbPrices = new UInt32[kBitModelTotal >> kNumMoveReducingBits]; + + static BitEncoder() + { + const int kNumBits = kNumBitModelTotalBits - kNumMoveReducingBits; + for (int i = kNumBits - 1; i >= 0; i--) + { + UInt32 start = (UInt32)1 << (kNumBits - i - 1); + UInt32 end = (UInt32)1 << (kNumBits - i); + for (UInt32 j = start; j < end; j++) + { + ProbPrices[j] = ((UInt32)i << kNumBitPriceShiftBits) + + (((end - j) << kNumBitPriceShiftBits) >> (kNumBits - i - 1)); + } + } + } + + public uint GetPrice(uint symbol) + { + return ProbPrices[(((Prob - symbol) ^ (-(int)symbol)) & (kBitModelTotal - 1)) >> kNumMoveReducingBits]; + } + public uint GetPrice0() { return ProbPrices[Prob >> kNumMoveReducingBits]; } + public uint GetPrice1() { return ProbPrices[(kBitModelTotal - Prob) >> kNumMoveReducingBits]; } + } + + internal struct BitDecoder + { + public const int kNumBitModelTotalBits = 11; + public const uint kBitModelTotal = 1 << kNumBitModelTotalBits; + private const int kNumMoveBits = 5; + + private uint Prob; + + public void UpdateModel(int numMoveBits, uint symbol) + { + if (symbol == 0) + { + Prob += (kBitModelTotal - Prob) >> numMoveBits; + } + else + { + Prob -= Prob >> numMoveBits; + } + } + + public void Init() { Prob = kBitModelTotal >> 1; } + + public uint Decode(Decoder rangeDecoder) + { + uint newBound = (rangeDecoder.Range >> kNumBitModelTotalBits) * Prob; + if (rangeDecoder.Code < newBound) + { + rangeDecoder.Range = newBound; + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 0; + } + else + { + rangeDecoder.Range -= newBound; + rangeDecoder.Code -= newBound; + Prob -= Prob >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 1; + } + } + } +} diff --git a/7zip/Compress/RangeCoder/RangeCoderBitTree.cs b/WPinternals/7zip/Compress/RangeCoder/RangeCoderBitTree.cs similarity index 96% rename from 7zip/Compress/RangeCoder/RangeCoderBitTree.cs rename to WPinternals/7zip/Compress/RangeCoder/RangeCoderBitTree.cs index a98084b..15b2750 100644 --- a/7zip/Compress/RangeCoder/RangeCoderBitTree.cs +++ b/WPinternals/7zip/Compress/RangeCoder/RangeCoderBitTree.cs @@ -1,164 +1,164 @@ -using System; - -namespace SevenZip.Compression.RangeCoder -{ - internal struct BitTreeEncoder - { - private readonly BitEncoder[] Models; - private readonly int NumBitLevels; - - public BitTreeEncoder(int numBitLevels) - { - NumBitLevels = numBitLevels; - Models = new BitEncoder[1 << numBitLevels]; - } - - public void Init() - { - for (uint i = 1; i < (1 << NumBitLevels); i++) - { - Models[i].Init(); - } - } - - public void Encode(Encoder rangeEncoder, UInt32 symbol) - { - UInt32 m = 1; - for (int bitIndex = NumBitLevels; bitIndex > 0;) - { - bitIndex--; - UInt32 bit = (symbol >> bitIndex) & 1; - Models[m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - } - } - - public void ReverseEncode(Encoder rangeEncoder, UInt32 symbol) - { - UInt32 m = 1; - for (UInt32 i = 0; i < NumBitLevels; i++) - { - UInt32 bit = symbol & 1; - Models[m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - symbol >>= 1; - } - } - - public UInt32 GetPrice(UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int bitIndex = NumBitLevels; bitIndex > 0;) - { - bitIndex--; - UInt32 bit = (symbol >> bitIndex) & 1; - price += Models[m].GetPrice(bit); - m = (m << 1) + bit; - } - return price; - } - - public UInt32 ReverseGetPrice(UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int i = NumBitLevels; i > 0; i--) - { - UInt32 bit = symbol & 1; - symbol >>= 1; - price += Models[m].GetPrice(bit); - m = (m << 1) | bit; - } - return price; - } - - public static UInt32 ReverseGetPrice(BitEncoder[] Models, UInt32 startIndex, - int NumBitLevels, UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int i = NumBitLevels; i > 0; i--) - { - UInt32 bit = symbol & 1; - symbol >>= 1; - price += Models[startIndex + m].GetPrice(bit); - m = (m << 1) | bit; - } - return price; - } - - public static void ReverseEncode(BitEncoder[] Models, UInt32 startIndex, - Encoder rangeEncoder, int NumBitLevels, UInt32 symbol) - { - UInt32 m = 1; - for (int i = 0; i < NumBitLevels; i++) - { - UInt32 bit = symbol & 1; - Models[startIndex + m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - symbol >>= 1; - } - } - } - - internal struct BitTreeDecoder - { - private readonly BitDecoder[] Models; - private readonly int NumBitLevels; - - public BitTreeDecoder(int numBitLevels) - { - NumBitLevels = numBitLevels; - Models = new BitDecoder[1 << numBitLevels]; - } - - public void Init() - { - for (uint i = 1; i < (1 << NumBitLevels); i++) - { - Models[i].Init(); - } - } - - public uint Decode(Decoder rangeDecoder) - { - uint m = 1; - for (int bitIndex = NumBitLevels; bitIndex > 0; bitIndex--) - { - m = (m << 1) + Models[m].Decode(rangeDecoder); - } - - return m - ((uint)1 << NumBitLevels); - } - - public uint ReverseDecode(Decoder rangeDecoder) - { - uint m = 1; - uint symbol = 0; - for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) - { - uint bit = Models[m].Decode(rangeDecoder); - m <<= 1; - m += bit; - symbol |= bit << bitIndex; - } - return symbol; - } - - public static uint ReverseDecode(BitDecoder[] Models, UInt32 startIndex, - Decoder rangeDecoder, int NumBitLevels) - { - uint m = 1; - uint symbol = 0; - for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) - { - uint bit = Models[startIndex + m].Decode(rangeDecoder); - m <<= 1; - m += bit; - symbol |= bit << bitIndex; - } - return symbol; - } - } -} +using System; + +namespace SevenZip.Compression.RangeCoder +{ + internal struct BitTreeEncoder + { + private readonly BitEncoder[] Models; + private readonly int NumBitLevels; + + public BitTreeEncoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new BitEncoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << NumBitLevels); i++) + { + Models[i].Init(); + } + } + + public void Encode(Encoder rangeEncoder, UInt32 symbol) + { + UInt32 m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0;) + { + bitIndex--; + UInt32 bit = (symbol >> bitIndex) & 1; + Models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + } + } + + public void ReverseEncode(Encoder rangeEncoder, UInt32 symbol) + { + UInt32 m = 1; + for (UInt32 i = 0; i < NumBitLevels; i++) + { + UInt32 bit = symbol & 1; + Models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } + + public UInt32 GetPrice(UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0;) + { + bitIndex--; + UInt32 bit = (symbol >> bitIndex) & 1; + price += Models[m].GetPrice(bit); + m = (m << 1) + bit; + } + return price; + } + + public UInt32 ReverseGetPrice(UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int i = NumBitLevels; i > 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += Models[m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static UInt32 ReverseGetPrice(BitEncoder[] Models, UInt32 startIndex, + int NumBitLevels, UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int i = NumBitLevels; i > 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += Models[startIndex + m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static void ReverseEncode(BitEncoder[] Models, UInt32 startIndex, + Encoder rangeEncoder, int NumBitLevels, UInt32 symbol) + { + UInt32 m = 1; + for (int i = 0; i < NumBitLevels; i++) + { + UInt32 bit = symbol & 1; + Models[startIndex + m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } + } + + internal struct BitTreeDecoder + { + private readonly BitDecoder[] Models; + private readonly int NumBitLevels; + + public BitTreeDecoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new BitDecoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << NumBitLevels); i++) + { + Models[i].Init(); + } + } + + public uint Decode(Decoder rangeDecoder) + { + uint m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; bitIndex--) + { + m = (m << 1) + Models[m].Decode(rangeDecoder); + } + + return m - ((uint)1 << NumBitLevels); + } + + public uint ReverseDecode(Decoder rangeDecoder) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= bit << bitIndex; + } + return symbol; + } + + public static uint ReverseDecode(BitDecoder[] Models, UInt32 startIndex, + Decoder rangeDecoder, int NumBitLevels) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[startIndex + m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= bit << bitIndex; + } + return symbol; + } + } +} diff --git a/7zip/ICoder.cs b/WPinternals/7zip/ICoder.cs similarity index 96% rename from 7zip/ICoder.cs rename to WPinternals/7zip/ICoder.cs index 5a09466..ac070a8 100644 --- a/7zip/ICoder.cs +++ b/WPinternals/7zip/ICoder.cs @@ -1,173 +1,173 @@ -// ICoder.h - -using System; -using System.Threading; - -namespace SevenZip -{ - /// - /// The exception that is thrown when an error in input stream occurs during decoding. - /// - internal class DataErrorException : ApplicationException - { - public DataErrorException() : base("Data Error") { } - - public DataErrorException(string message) : base(message) - { - } - - public DataErrorException(string message, Exception innerException) : base(message, innerException) - { - } - } - - /// - /// The exception that is thrown when the value of an argument is outside the allowable range. - /// - internal class InvalidParamException : ApplicationException - { - public InvalidParamException() : base("Invalid Parameter") { } - - public InvalidParamException(string message) : base(message) - { - } - - public InvalidParamException(string message, Exception innerException) : base(message, innerException) - { - } - } - - public interface ICodeProgress - { - /// - /// Callback progress. - /// - /// - /// input size. -1 if unknown. - /// - /// - /// output size. -1 if unknown. - /// - void SetProgress(Int64 inSize, Int64 outSize); - }; - - public interface ICoder - { - /// - /// Codes streams. - /// - /// - /// input Stream. - /// - /// - /// output Stream. - /// - /// - /// input Size. -1 if unknown. - /// - /// - /// output Size. -1 if unknown. - /// - /// - /// callback progress reference. - /// - /// - /// if input stream is not valid - /// - void Code(System.IO.Stream inStream, System.IO.Stream outStream, - Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken? token = null); - }; - - /* - public interface ICoder2 - { - void Code(ISequentialInStream []inStreams, - const UInt64 []inSizes, - ISequentialOutStream []outStreams, - UInt64 []outSizes, - ICodeProgress progress); - }; - */ - - /// - /// Provides the fields that represent properties idenitifiers for compressing. - /// - public enum CoderPropID - { - /// - /// Specifies default property. - /// - DefaultProp = 0, - /// - /// Specifies size of dictionary. - /// - DictionarySize, - /// - /// Specifies size of memory for PPM*. - /// - UsedMemorySize, - /// - /// Specifies order for PPM methods. - /// - Order, - /// - /// Specifies Block Size. - /// - BlockSize, - /// - /// Specifies number of postion state bits for LZMA (0 <= x <= 4). - /// - PosStateBits, - /// - /// Specifies number of literal context bits for LZMA (0 <= x <= 8). - /// - LitContextBits, - /// - /// Specifies number of literal position bits for LZMA (0 <= x <= 4). - /// - LitPosBits, - /// - /// Specifies number of fast bytes for LZ*. - /// - NumFastBytes, - /// - /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B". - /// - MatchFinder, - /// - /// Specifies the number of match finder cyckes. - /// - MatchFinderCycles, - /// - /// Specifies number of passes. - /// - NumPasses, - /// - /// Specifies number of algorithm. - /// - Algorithm, - /// - /// Specifies the number of threads. - /// - NumThreads, - /// - /// Specifies mode with end marker. - /// - EndMarker - }; - - public interface ISetCoderProperties - { - void SetCoderProperties(CoderPropID[] propIDs, object[] properties); - }; - - public interface IWriteCoderProperties - { - void WriteCoderProperties(System.IO.Stream outStream); - } - - public interface ISetDecoderProperties - { - void SetDecoderProperties(byte[] properties); - } -} +// ICoder.h + +using System; +using System.Threading; + +namespace SevenZip +{ + /// + /// The exception that is thrown when an error in input stream occurs during decoding. + /// + internal class DataErrorException : ApplicationException + { + public DataErrorException() : base("Data Error") { } + + public DataErrorException(string message) : base(message) + { + } + + public DataErrorException(string message, Exception innerException) : base(message, innerException) + { + } + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range. + /// + internal class InvalidParamException : ApplicationException + { + public InvalidParamException() : base("Invalid Parameter") { } + + public InvalidParamException(string message) : base(message) + { + } + + public InvalidParamException(string message, Exception innerException) : base(message, innerException) + { + } + } + + public interface ICodeProgress + { + /// + /// Callback progress. + /// + /// + /// input size. -1 if unknown. + /// + /// + /// output size. -1 if unknown. + /// + void SetProgress(Int64 inSize, Int64 outSize); + }; + + public interface ICoder + { + /// + /// Codes streams. + /// + /// + /// input Stream. + /// + /// + /// output Stream. + /// + /// + /// input Size. -1 if unknown. + /// + /// + /// output Size. -1 if unknown. + /// + /// + /// callback progress reference. + /// + /// + /// if input stream is not valid + /// + void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken? token = null); + }; + + /* + public interface ICoder2 + { + void Code(ISequentialInStream []inStreams, + const UInt64 []inSizes, + ISequentialOutStream []outStreams, + UInt64 []outSizes, + ICodeProgress progress); + }; + */ + + /// + /// Provides the fields that represent properties idenitifiers for compressing. + /// + public enum CoderPropID + { + /// + /// Specifies default property. + /// + DefaultProp = 0, + /// + /// Specifies size of dictionary. + /// + DictionarySize, + /// + /// Specifies size of memory for PPM*. + /// + UsedMemorySize, + /// + /// Specifies order for PPM methods. + /// + Order, + /// + /// Specifies Block Size. + /// + BlockSize, + /// + /// Specifies number of postion state bits for LZMA (0 <= x <= 4). + /// + PosStateBits, + /// + /// Specifies number of literal context bits for LZMA (0 <= x <= 8). + /// + LitContextBits, + /// + /// Specifies number of literal position bits for LZMA (0 <= x <= 4). + /// + LitPosBits, + /// + /// Specifies number of fast bytes for LZ*. + /// + NumFastBytes, + /// + /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B". + /// + MatchFinder, + /// + /// Specifies the number of match finder cyckes. + /// + MatchFinderCycles, + /// + /// Specifies number of passes. + /// + NumPasses, + /// + /// Specifies number of algorithm. + /// + Algorithm, + /// + /// Specifies the number of threads. + /// + NumThreads, + /// + /// Specifies mode with end marker. + /// + EndMarker + }; + + public interface ISetCoderProperties + { + void SetCoderProperties(CoderPropID[] propIDs, object[] properties); + }; + + public interface IWriteCoderProperties + { + void WriteCoderProperties(System.IO.Stream outStream); + } + + public interface ISetDecoderProperties + { + void SetDecoderProperties(byte[] properties); + } +} diff --git a/App.xaml b/WPinternals/App.xaml similarity index 98% rename from App.xaml rename to WPinternals/App.xaml index cedbe95..be1156e 100644 --- a/App.xaml +++ b/WPinternals/App.xaml @@ -1,112 +1,112 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/App.xaml.cs b/WPinternals/App.xaml.cs similarity index 97% rename from App.xaml.cs rename to WPinternals/App.xaml.cs index 1542850..125bd0c 100644 --- a/App.xaml.cs +++ b/WPinternals/App.xaml.cs @@ -1,103 +1,103 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Windows; - -namespace WPinternals -{ - /// - /// Interaction logic for App.xaml - /// - public partial class App : Application - { - internal static Action NavigateToGettingStarted; - internal static Action NavigateToUnlockBoot; - internal static PatchEngine PatchEngine; - internal static WPinternalsConfig Config; - internal static Mutex mutex = new(false, "Global\\WPinternalsRunning"); - internal static DownloadsViewModel DownloadManager; - internal static bool InterruptBoot = false; - internal static bool IsPnPEventLogMissing = true; - - public App() - : base() - { - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - -#if NETCORE - System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); -#endif - - if (Environment.GetCommandLineArgs().Length > 1) - { - CommandLine.OpenConsole(); - } - - try - { - if (!mutex.WaitOne(0, false)) - { - if (Environment.GetCommandLineArgs().Length > 1) - { - Console.WriteLine("Windows Phone Internals is already running"); - CommandLine.CloseConsole(); - } - else - { - MessageBox.Show("Windows Phone Internals is already running.", "Windows Phone Internals", MessageBoxButton.OK, MessageBoxImage.Exclamation); - } - - Environment.Exit(0); - } - } - catch (AbandonedMutexException) { } - - Registration.CheckExpiration(); - - string PatchDefintionsXml; - string PatchDefintionsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PatchDefintions.xml"); - if (File.Exists(PatchDefintionsPath)) - { - PatchDefintionsXml = File.ReadAllText(PatchDefintionsPath); - } - else - { - using Stream stream = System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream("WPinternals.PatchDefinitions.xml"); - using StreamReader sr = new(stream); - PatchDefintionsXml = sr.ReadToEnd(); - } - PatchEngine = new PatchEngine(PatchDefintionsXml); - - Config = WPinternalsConfig.ReadConfig(); - } - - private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - if (e.ExceptionObject is Exception) - { - LogFile.LogException(e.ExceptionObject as Exception); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Windows; + +namespace WPinternals +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + internal static Action NavigateToGettingStarted; + internal static Action NavigateToUnlockBoot; + internal static PatchEngine PatchEngine; + internal static WPinternalsConfig Config; + internal static Mutex mutex = new(false, "Global\\WPinternalsRunning"); + internal static DownloadsViewModel DownloadManager; + internal static bool InterruptBoot = false; + internal static bool IsPnPEventLogMissing = true; + + public App() + : base() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + +#if NETCORE + System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); +#endif + + if (Environment.GetCommandLineArgs().Length > 1) + { + CommandLine.OpenConsole(); + } + + try + { + if (!mutex.WaitOne(0, false)) + { + if (Environment.GetCommandLineArgs().Length > 1) + { + Console.WriteLine("Windows Phone Internals is already running"); + CommandLine.CloseConsole(); + } + else + { + MessageBox.Show("Windows Phone Internals is already running.", "Windows Phone Internals", MessageBoxButton.OK, MessageBoxImage.Exclamation); + } + + Environment.Exit(0); + } + } + catch (AbandonedMutexException) { } + + Registration.CheckExpiration(); + + string PatchDefintionsXml; + string PatchDefintionsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PatchDefintions.xml"); + if (File.Exists(PatchDefintionsPath)) + { + PatchDefintionsXml = File.ReadAllText(PatchDefintionsPath); + } + else + { + using Stream stream = System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream("WPinternals.PatchDefinitions.xml"); + using StreamReader sr = new(stream); + PatchDefintionsXml = sr.ReadToEnd(); + } + PatchEngine = new PatchEngine(PatchDefintionsXml); + + Config = WPinternalsConfig.ReadConfig(); + } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + if (e.ExceptionObject is Exception) + { + LogFile.LogException(e.ExceptionObject as Exception); + } + } + } +} diff --git a/CommandLine.cs b/WPinternals/CommandLine.cs similarity index 98% rename from CommandLine.cs rename to WPinternals/CommandLine.cs index d2328ee..39992ba 100644 --- a/CommandLine.cs +++ b/WPinternals/CommandLine.cs @@ -1,2109 +1,2109 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; - -namespace WPinternals -{ - internal static class CommandLine - { - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool AllocConsole(); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool AttachConsole(int dwProcessId); - - [DllImport("kernel32.dll")] - private static extern IntPtr GetConsoleWindow(); - - [DllImport("user32.dll")] - private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); - - private const UInt32 StdOutputHandle = 0xFFFFFFF5; - - [DllImport("kernel32.dll")] - private static extern IntPtr GetStdHandle(UInt32 nStdHandle); - - [DllImport("kernel32.dll")] - private static extern void SetStdHandle(UInt32 nStdHandle, IntPtr handle); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, uint lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, uint hTemplateFile); - - [DllImport("user32.dll")] - private static extern IntPtr GetForegroundWindow(); - - private const int SW_HIDE = 0; - private const int SW_SHOW = 5; - - private const int MY_CODE_PAGE = 437; - private const uint GENERIC_WRITE = 0x40000000; - private const uint FILE_SHARE_WRITE = 0x2; - private const uint OPEN_EXISTING = 0x3; - - internal static bool IsConsoleVisible = false; - internal static bool IsNewConsoleCreated = false; - - private static IntPtr hConsoleWnd; - - [DllImport("User32.Dll", EntryPoint = "PostMessageA")] - private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam); - - private const int VK_RETURN = 0x0D; - private const int WM_KEYDOWN = 0x100; - - /// - /// When the main window should not be shown, this function should call ExitProcess and it should not return. - /// - internal static async Task ParseCommandLine(System.Threading.SynchronizationContext UIContext) - { - FFU FFU = null; - PhoneNotifierViewModel Notifier; - NokiaFlashModel FlashModel; - NokiaPhoneModel NormalModel; - PhoneInfo Info; - string ProductType; - string ProductCode; - string OperatorCode; - string DownloadFolder; - string FFUFilePath; - string URL; - string[] URLs; - Uri URI; - string FFUFileName; - string EmergencyFileName; - string EmergencyFilePath; - string ProgrammerPath = ""; - string PayloadPath = ""; - string EfiEspImagePath = null; - string MainOsImagePath = null; - DiscUtils.Fat.FatFileSystem UnlockedEFIESPFileSystem; - DiscUtils.Ntfs.NtfsFileSystem UnlockedMainOsFileSystem; - bool PatchResult; - - try - { - string[] args = Environment.GetCommandLineArgs(); - - if (args.Length == 1) - { - return; - } - - switch (args[1].ToLower().TrimStart(new char[] { '-', '/' })) - { -#if DEBUG - case "test": - LogFile.BeginAction("Test"); - await TestCode.Test(UIContext); - LogFile.EndAction("Test"); - break; -#endif - case "flashpartition": - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashPartition "); - } - - if (args.Length >= 5) - { - await LumiaV2UnlockBootViewModel.LumiaV2FlashPartition(UIContext, args[4], args[2], args[3]); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2FlashPartition(UIContext, null, args[2], args[3]); - } - - break; - case "flashraw": - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashRaw "); - } - - UInt64 StartSector = 0; - try - { - StartSector = args[2].StartsWith("0x", StringComparison.OrdinalIgnoreCase) - ? Convert.ToUInt64(args[2][2..], 16) - : Convert.ToUInt64(args[2], 10); - } - catch - { - LogFile.Log("Bad start sector", LogType.ConsoleOnly); - break; - } - if (args.Length >= 5) - { - await LumiaV2UnlockBootViewModel.LumiaV2FlashRaw(UIContext, StartSector, args[3], args[4]); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2FlashRaw(UIContext, StartSector, args[3], null); - } - - break; - case "flashpartitionimmediately": - if (args.Length < 5) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashPartition "); - } - - await LumiaV2UnlockBootViewModel.LumiaV2FlashPartition(UIContext, args[4], args[2], args[3], false); - break; - case "readgpt": - LogFile.BeginAction("ReadGPT"); - try - { - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Bootloader); // This also works for Bootloader Spec A - - GPT GPT = FlashModel.ReadGPT(); // May throw NotSupportedException - foreach (Partition Partition in GPT.Partitions) - { - LogFile.Log(Partition.Name.PadRight(20) + "0x" + Partition.FirstSector.ToString("X8") + " - 0x" + Partition.LastSector.ToString("X8") + " " + Partition.Volume, LogType.ConsoleOnly); - } - - if (FlashModel.ReadPhoneInfo(false).FlashAppProtocolVersionMajor >= 2) - { - FlashModel.SwitchToFlashAppContext(); - } - else - { - await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - } - - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("ReadGPT"); - } - break; - case "backupgpt": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -BackupGPT "); - } - - LogFile.BeginAction("BackupGPT"); - try - { - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - GPT GPT = FlashModel.ReadGPT(); // May throw NotSupportedException - Directory.CreateDirectory(Path.GetDirectoryName(args[2])); - GPT.WritePartitions(args[2]); - FlashModel.SwitchToFlashAppContext(); - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("BackupGPT"); - } - break; - case "restoregpt": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RestoreGPT "); - } - - LogFile.BeginAction("RestoreGPT"); - try - { - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - byte[] GptChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); - GPT GPT = new(GptChunk); - string Xml = File.ReadAllText(args[2]); - GPT.MergePartitions(Xml, false); - GPT.Rebuild(); - await LumiaV2UnlockBootViewModel.LumiaV2CustomFlash(Notifier, null, false, false, 0, GptChunk, true, true); - FlashModel.SwitchToFlashAppContext(); - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("RestoreGPT"); - } - break; - case "mergegpt": - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -MergeGPT Or use: WPinternals.exe -MergeGPT "); - } - - LogFile.BeginAction("MergeGPT"); - try - { - GPT GPT = GPT.ReadPartitions(args[2]); - - ZipArchive Archive = null; - FileStream s = null; - try - { - s = new FileStream(args[3], FileMode.Open, FileAccess.Read); - Archive = new ZipArchive(s); - } - catch { } - - if (Archive == null) - { - s?.Close(); - - // Assume Xml-file - GPT.MergePartitionsFromFile(args[3], true); - } - else - { - ZipArchiveEntry PartitionEntry = Archive.GetEntry("Partitions.xml"); - if (PartitionEntry == null) - { - GPT.MergePartitions(null, true, Archive); - } - else - { - using Stream ZipStream = PartitionEntry.Open(); - using StreamReader ZipReader = new(ZipStream); - string PartitionXml = ZipReader.ReadToEnd(); - GPT.MergePartitions(PartitionXml, true, Archive); - } - } - - Archive?.Dispose(); - - if (args.Length >= 5) - { - GPT.WritePartitions(args[4]); - } - - foreach (Partition Partition in GPT.Partitions) - { - LogFile.Log(Partition.Name.PadRight(20) + "0x" + Partition.FirstSector.ToString("X8") + " - 0x" + Partition.LastSector.ToString("X8") + " " + Partition.Volume, LogType.ConsoleOnly); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("MergeGPT"); - } - break; - case "dumpffu": - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DumpFFU "); - } - - FFU = new FFU(args[2]); - if (args.Length < 5) - { - foreach (Partition Partition in FFU.GPT.Partitions) - { - if (FFU.IsPartitionPresentInFFU(Partition.Name)) - { - FFU.WritePartition(Partition.Name, Path.Combine(args[3], Partition.Name + ".bin")); - } - } - } - else - { - Partition Target = FFU.GPT.GetPartition(args[4]); - if ((Target == null) || (!FFU.IsPartitionPresentInFFU(Target.Name))) - { - throw new InvalidOperationException("Partition not found in FFU!"); - } - - FFU.WritePartition(Target.Name, Path.Combine(args[3], Target.Name + ".bin")); - } - break; - case "dumpuefi": - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DumpUEFI "); - } - - byte[] UefiBinary; - if (FFU.IsFFU(args[2])) - { - FFU = new FFU(args[2]); - UefiBinary = FFU.GetPartition("UEFI"); - } - else - { - UefiBinary = File.ReadAllBytes(args[2]); - } - - UEFI UEFI = new(UefiBinary); - - foreach (EFI Efi in UEFI.EFIs) - { - byte[] EfiBinary = UEFI.GetFile(Efi.Guid); - string Name = Efi.Name ?? Efi.Guid.ToString(); - - if (!Name.Contains('.')) - { - Name += Efi.Type switch - { - 5 or 7 => ".dll", - 9 => ".exe", - _ => ".bin", - }; - } - string EfiPath = Path.Combine(args[3], Name); - Directory.CreateDirectory(Path.GetDirectoryName(EfiPath)); - File.WriteAllBytes(EfiPath, EfiBinary); - } - break; - case "testprogrammer": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -TestProgrammer "); - } - - await TestCode.TestProgrammer(UIContext, args[2]); - break; - case "findflashingprofile": - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - if (args.Length > 2) - { - await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, args[2]); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, null); - } - - Notifier.Stop(); - break; - case "findflashingprofileexperimental": - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - if (args.Length > 2) - { - await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, args[2], Experimental: true); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, null, Experimental: true); - } - - Notifier.Stop(); - break; - case "findflashingprofilenorestart": - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - if (args.Length > 2) - { - await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, args[2], false); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, null, false); - } - - Notifier.Stop(); - break; - case "enabletestsigning": - if (args.Length > 2) - { - await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, args[2]); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, null); - } - - break; - case "enabletestsigningnorestart": - if (args.Length > 2) - { - await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, args[2], false); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, null, false); - } - - break; - case "clearnv": - if (args.Length > 2) - { - await LumiaV2UnlockBootViewModel.LumiaV2ClearNV(UIContext, args[2]); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2ClearNV(UIContext, null); - } - - break; - case "switchtomassstoragemode": - LogFile.BeginAction("SwitchToMassStorageMode"); - try - { - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - LogFile.Log("Command: Switch to Mass Storage Mode", LogType.FileAndConsole); - if (args.Length > 2) - { - await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, args[2]); - } - else - { - await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, null); - } - - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("SwitchToMassStorageMode"); - } - break; - case "relockphone": - Notifier = new PhoneNotifierViewModel(); - try - { - UIContext.Send(s => Notifier.Start(), null); - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - Info = FlashModel.ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - - FFU ProfileFFU = null; - FFU CurrentFFU; - for (int i = 2; i <= 3; i++) - { - if (args.Length > i) - { - CurrentFFU = new FFU(args[i]); - string CurrentVersion = CurrentFFU.GetOSVersion(); - string PlatformID = CurrentFFU.PlatformID; - - // Check if the current FFU matches the connected phone, so that the FFU can be used for profiling. - if (Info.PlatformID.StartsWith(PlatformID, StringComparison.OrdinalIgnoreCase)) - { - ProfileFFU = CurrentFFU; - } - } - } - - if (ProfileFFU == null) - { - List FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); - ProfileFFU = FFUs.Count > 0 - ? new FFU(FFUs[0].Path) - : throw new WPinternalsException("Profile FFU missing", "No profile FFU has been found in the repository for your device. You can add a profile FFU within the download section of the tool or by using the command line."); - } - LogFile.Log("Profile FFU: " + ProfileFFU.Path); - - UIContext.Send(s => Notifier.Start(), null); - - await LumiaUnlockBootloaderViewModel.LumiaRelockUEFI(Notifier, ProfileFFU.Path); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - Notifier.Stop(); - break; - case "addffu": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -AddFFU "); - } - - App.Config.AddFfuToRepository(args[2]); - break; - case "removeffu": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RemoveFFU "); - } - - App.Config.RemoveFfuFromRepository(args[2]); - break; - case "addemergency": - if (args.Length < 5) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -AddEmergnency "); - } - - App.Config.AddEmergencyToRepository(args[2], args[3], args[4]); - break; - case "removeemergency": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RemoveEmergency "); - } - - App.Config.RemoveEmergencyFromRepository(args[2]); - break; - case "listrepository": - int Count = 0; - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log("FFU Repository:", LogType.ConsoleOnly); - foreach (FFUEntry Entry in App.Config.FFURepository) - { - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log("FFU " + Count.ToString() + ":", LogType.ConsoleOnly); - LogFile.Log("File: " + Entry.Path + (Entry.Exists() ? "" : " (file is missing)"), LogType.ConsoleOnly); - LogFile.Log("Platform ID: " + Entry.PlatformID, LogType.ConsoleOnly); - if (Entry.FirmwareVersion != null) - { - LogFile.Log("Firmware version: " + Entry.FirmwareVersion, LogType.ConsoleOnly); - } - - if (Entry.OSVersion != null) - { - LogFile.Log("OS version: " + Entry.OSVersion, LogType.ConsoleOnly); - } - - Count++; - } - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log("Emergency Repository:", LogType.ConsoleOnly); - Count = 0; - foreach (EmergencyFileEntry Entry in App.Config.EmergencyRepository) - { - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log("Emergency " + Count.ToString() + ":", LogType.ConsoleOnly); - LogFile.Log("Type: " + Entry.Type, LogType.ConsoleOnly); - LogFile.Log("Programmer file: " + Entry.ProgrammerPath + (Entry.ProgrammerExists() ? "" : " (file is missing)"), LogType.ConsoleOnly); - if (Entry.PayloadPath != null) - { - LogFile.Log("Payload file: " + Entry.PayloadPath + (Entry.PayloadExists() ? "" : " (file is missing)"), LogType.ConsoleOnly); - } - - Count++; - } - break; - case "showffu": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -ShowFFU "); - } - - string FFUPath = args[2]; - LogFile.Log("FFU: " + FFUPath, LogType.ConsoleOnly); - FFU = new FFU(FFUPath); - - // Show basics - LogFile.Log("Platform ID: " + FFU.PlatformID, LogType.ConsoleOnly); - string Firmware = FFU.GetFirmwareVersion(); - if (Firmware != null) - { - LogFile.Log("Firmware version: " + Firmware, LogType.ConsoleOnly); - } - - string OSVersion = FFU.GetOSVersion(); - if (OSVersion != null) - { - LogFile.Log("OS version: " + OSVersion, LogType.ConsoleOnly); - } - - // Show partitions from GPT (also show which partitions are in the FFU payload) - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log("Partition table:", LogType.ConsoleOnly); - LogFile.Log("Name".PadRight(20) + "Start-sector".PadRight(20) + "End-sector".PadRight(20) + "Present in FFU", LogType.ConsoleOnly); - foreach (Partition p in FFU.GPT.Partitions) - { - LogFile.Log(p.Name.PadRight(20) + ("0x" + p.FirstSector.ToString("X16")).PadRight(20) + ("0x" + p.LastSector.ToString("X16")).PadRight(20) + (FFU.IsPartitionPresentInFFU(p.Name) ? "Yes" : "No"), LogType.ConsoleOnly); - } - break; - case "showphoneinfo": - LogFile.Log("Command: Show phone info", LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - Info = FlashModel.ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - Notifier.Stop(); - break; - case "unlockbootloader": - LogFile.BeginAction("UnlockBootloader"); - try - { - LogFile.Log("Command: Unlock Bootloader", LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - Info = FlashModel.ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - - FFU ProfileFFU = null; - FFU SupportedFFU = null; - FFU CurrentFFU; - for (int i = 2; i <= 3; i++) - { - if (args.Length > i) - { - CurrentFFU = new FFU(args[i]); - string CurrentVersion = CurrentFFU.GetOSVersion(); - string PlatformID = CurrentFFU.PlatformID; - - // Check if the current FFU matches the connected phone, so that the FFU can be used for profiling. - if (Info.PlatformID.StartsWith(PlatformID, StringComparison.OrdinalIgnoreCase)) - { - ProfileFFU = CurrentFFU; - } - - // Check if the current FFU is supported for unlocking. - if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == CurrentVersion)) - { - SupportedFFU = CurrentFFU; - } - } - } - - if (ProfileFFU == null) - { - List FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); - ProfileFFU = FFUs.Count > 0 - ? new FFU(FFUs[0].Path) - : throw new WPinternalsException("Profile FFU missing", "No profile FFU has been found in the repository for your device. You can add a profile FFU within the download section of the tool or by using the command line."); - } - LogFile.Log("Profile FFU: " + ProfileFFU.Path); - - if (SupportedFFU == null) - { - List FFUs = App.Config.FFURepository.Where(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion)).ToList(); - SupportedFFU = FFUs.Count > 0 - ? new FFU(FFUs[0].Path) - : throw new WPinternalsException("No donor-FFU found with supported OS version", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); - } - - await LumiaUnlockBootloaderViewModel.LumiaUnlockUEFI(Notifier, ProfileFFU.Path, null, SupportedFFU.Path); - - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("UnlockBootloader"); - } - break; - case "flashcustomrom": - LogFile.BeginAction("FlashCustomROM"); - try - { - LogFile.Log("Command: Flash Custom ROM", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashCustomROM "); - } - - string CustomRomPath = args[2]; - LogFile.Log("Custom ROM: " + CustomRomPath, LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - Info = FlashModel.ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - LogFile.Log("Preparing to flash Custom ROM", LogType.FileAndConsole); - await LumiaV2UnlockBootViewModel.LumiaV2FlashArchive(Notifier, CustomRomPath); - LogFile.Log("Custom ROM flashed successfully", LogType.FileAndConsole); - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("FlashCustomROM"); - } - break; - case "flashffu": - LogFile.BeginAction("FlashFFU"); - try - { - LogFile.Log("Command: Flash FFU", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashFFU "); - } - - FFUPath = args[2]; - LogFile.Log("FFU file: " + FFUPath, LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - Info = FlashModel.ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - LogFile.Log("Flashing FFU...", LogType.FileAndConsole); - await Task.Run(() => FlashModel.FlashFFU(new FFU(FFUPath), true, (byte)(!Info.IsBootloaderSecure ? FlashOptions.SkipSignatureCheck : 0))); - LogFile.Log("FFU flashed successfully", LogType.FileAndConsole); - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("FlashFFU"); - } - break; - case "fixbootafterunlockingbootloader": - LogFile.BeginAction("FixBoot"); - try - { - LogFile.Log("Command: Fix boot after unlocking bootloader", LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - string Drive = await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, null); - Notifier.Stop(); - App.PatchEngine.TargetPath = Drive + "\\"; - PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the MainOS partition of your phone. Make sure your phone runs a supported Operating System version."); - } - - LogFile.Log("Fixed bootloader", LogType.FileAndConsole); - LogFile.Log("The phone is left in Mass Storage mode", LogType.FileAndConsole); - LogFile.Log("Press and hold the power-button of the phone for at least 10 seconds to reset the phone", LogType.FileAndConsole); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("FixBoot"); - } - break; - case "enablerootaccess": - LogFile.BeginAction("EnableRootAccess"); - try - { - LogFile.Log("Command: Enable root access on the phone", LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - string Drive = await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, null); - Notifier.Stop(); - App.PatchEngine.TargetPath = Drive + "\\EFIESP\\"; - PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); - if (!PatchResult) - { - throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the EFIESP partition of your phone. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported."); - } - - App.PatchEngine.TargetPath = Drive + "\\"; - PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the MainOS partition of your phone. Make sure your phone runs a supported Operating System version."); - } - - PatchResult = App.PatchEngine.Patch("RootAccess-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Patch failed", "An error occured while modifying Operating System files on the MainOS partition of your phone for Root Access. Make sure your phone runs a supported Operating System version."); - } - - LogFile.Log("Root Access enabled!", LogType.FileAndConsole); - LogFile.Log("The phone is left in Mass Storage mode", LogType.FileAndConsole); - LogFile.Log("Press and hold the power-button of the phone for at least 10 seconds to reset the phone", LogType.FileAndConsole); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("EnableRootAccess"); - } - break; - case "unlockbootloaderonimage": - LogFile.Log("Command: Unlock bootloader on image", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -UnlockBootLoaderOnImage "); - } - - FFUFilePath = null; - EfiEspImagePath = args[2]; - if (args.Length > 3) - { - if (FFU.IsFFU(args[3])) - { - FFUFilePath = args[3]; - } - else - { - MainOsImagePath = args[3]; - } - } - if (args.Length > 4) - { - FFUFilePath = args[4]; - } - - using (FileStream FileSystemStream = new(EfiEspImagePath, FileMode.Open, FileAccess.ReadWrite)) - { - UnlockedEFIESPFileSystem = new DiscUtils.Fat.FatFileSystem(FileSystemStream); - - if (FFUFilePath != null) - { - FFU SupportedFFU = new(FFUFilePath); - LogFile.Log("Donor FFU: " + SupportedFFU.Path); - byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); - DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); - DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); - MemoryStream SupportedMobileStartupMemStream = new(); - SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); - byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); - SupportedMobileStartupMemStream.Close(); - SupportedMobileStartupStream.Close(); - - // Save supported mobilestartup.efi - LogFile.Log("Taking mobilestartup.efi from donor-FFU"); - Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write); - MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); - MobileStartupStream.Close(); - } - - App.PatchEngine.TargetImage = UnlockedEFIESPFileSystem; - PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); - } - - // Edit BCD - LogFile.Log("Edit BCD"); - using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"\efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite); - using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); - DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); - DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); - DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - - DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); - NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - } - - if (MainOsImagePath != null) - { - using FileStream FileSystemStream = new(MainOsImagePath, FileMode.Open, FileAccess.ReadWrite); - UnlockedMainOsFileSystem = new DiscUtils.Ntfs.NtfsFileSystem(FileSystemStream); - - App.PatchEngine.TargetImage = UnlockedMainOsFileSystem; - PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); - } - } - LogFile.Log("Bootloader unlocked on image", LogType.FileAndConsole); - break; - case "enablerootaccessonimage": - LogFile.Log("Command: Enable root access on image", LogType.FileAndConsole); - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -EnableRootAccessOnImage "); - } - - FFUFilePath = null; - EfiEspImagePath = args[2]; - MainOsImagePath = args[3]; - if (args.Length > 4) - { - FFUFilePath = args[4]; - } - - using (FileStream FileSystemStream = new(EfiEspImagePath, FileMode.Open, FileAccess.ReadWrite)) - { - UnlockedEFIESPFileSystem = new DiscUtils.Fat.FatFileSystem(FileSystemStream); - - if (FFUFilePath != null) - { - FFU SupportedFFU = new(FFUFilePath); - LogFile.Log("Supported FFU: " + SupportedFFU.Path); - byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); - DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); - DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); - MemoryStream SupportedMobileStartupMemStream = new(); - SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); - byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); - SupportedMobileStartupMemStream.Close(); - SupportedMobileStartupStream.Close(); - - // Save supported mobilestartup.efi - LogFile.Log("Taking mobilestartup.efi from donor-FFU"); - Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write); - MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); - MobileStartupStream.Close(); - } - - App.PatchEngine.TargetImage = UnlockedEFIESPFileSystem; - PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); - } - - // Edit BCD - LogFile.Log("Edit BCD"); - using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"\efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite); - using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); - DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); - DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); - DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - - DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); - NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - } - - using (FileStream FileSystemStream = new(MainOsImagePath, FileMode.Open, FileAccess.ReadWrite)) - { - UnlockedMainOsFileSystem = new DiscUtils.Ntfs.NtfsFileSystem(FileSystemStream); - - App.PatchEngine.TargetImage = UnlockedMainOsFileSystem; - PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); - } - - PatchResult = App.PatchEngine.Patch("RootAccess-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch MainOS", "An error occured while modifying Operating System files on the MainOS partition you provided for Root Access. Make sure your phone runs a supported Operating System version."); - } - } - LogFile.Log("Root access enabled on image", LogType.FileAndConsole); - break; - case "unlockbootloaderonmountedimage": - LogFile.Log("Command: Unlock bootloader on mounted image", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -UnlockBootLoaderOnMountedImage "); - } - - FFUFilePath = null; - EfiEspImagePath = args[2]; - if (args.Length > 3) - { - if (FFU.IsFFU(args[3])) - { - FFUFilePath = args[3]; - } - else - { - MainOsImagePath = args[3]; - } - } - if (args.Length > 4) - { - FFUFilePath = args[4]; - } - - if (FFUFilePath != null) - { - FFU SupportedFFU = new(FFUFilePath); - LogFile.Log("Donor-FFU: " + SupportedFFU.Path); - byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); - DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); - DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); - MemoryStream SupportedMobileStartupMemStream = new(); - SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); - byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); - SupportedMobileStartupMemStream.Close(); - SupportedMobileStartupStream.Close(); - - // Save supported mobilestartup.efi - LogFile.Log("Taking mobilestartup.efi from donor-FFU"); - Stream MobileStartupStream = File.Open(Path.Combine(EfiEspImagePath, @"Windows\System32\Boot\mobilestartup.efi"), FileMode.Create, FileAccess.Write); - MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); - MobileStartupStream.Close(); - } - - App.PatchEngine.TargetPath = EfiEspImagePath; - PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); - } - - // Edit BCD - LogFile.Log("Edit BCD"); - using (Stream BCDFileStream = File.Open(Path.Combine(EfiEspImagePath, @"efi\Microsoft\Boot\BCD"), FileMode.Open, FileAccess.ReadWrite)) - { - using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); - DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); - DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); - DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - - DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); - NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - } - - if (MainOsImagePath != null) - { - App.PatchEngine.TargetPath = MainOsImagePath; - PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); - } - } - LogFile.Log("Bootloader unlocked on image", LogType.FileAndConsole); - break; - case "enablerootaccessonmountedimage": - LogFile.Log("Command: Enable root access on mounted image", LogType.FileAndConsole); - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -EnableRootAccessOnMountedImage "); - } - - FFUFilePath = null; - EfiEspImagePath = args[2]; - MainOsImagePath = args[3]; - if (args.Length > 4) - { - FFUFilePath = args[4]; - } - - if (FFUFilePath != null) - { - FFU SupportedFFU = new(FFUFilePath); - LogFile.Log("Supported FFU: " + SupportedFFU.Path); - byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); - DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); - DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); - MemoryStream SupportedMobileStartupMemStream = new(); - SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); - byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); - SupportedMobileStartupMemStream.Close(); - SupportedMobileStartupStream.Close(); - - // Save supported mobilestartup.efi - LogFile.Log("Taking mobilestartup.efi from donor-FFU"); - Stream MobileStartupStream = File.Open(Path.Combine(EfiEspImagePath, @"Windows\System32\Boot\mobilestartup.efi"), FileMode.Create, FileAccess.Write); - MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); - MobileStartupStream.Close(); - } - - App.PatchEngine.TargetPath = EfiEspImagePath; - PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); - } - - // Edit BCD - LogFile.Log("Edit BCD"); - using (Stream BCDFileStream = File.Open(Path.Combine(EfiEspImagePath, @"efi\Microsoft\Boot\BCD"), FileMode.Open, FileAccess.ReadWrite)) - { - using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); - DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); - DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); - DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - - DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); - NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); - if (NoCodeIntegrityElement != null) - { - NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); - } - else - { - WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); - } - } - - App.PatchEngine.TargetPath = MainOsImagePath; - PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); - } - - PatchResult = App.PatchEngine.Patch("RootAccess-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Failed to patch MainOS", "An error occured while modifying Operating System files on the MainOS partition you provided for Root Access. Make sure your phone runs a supported Operating System version."); - } - - LogFile.Log("Root access enabled on image", LogType.FileAndConsole); - break; - case "downloadffu": - LogFile.Log("Command: Download FFU", LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) - { - NormalModel = (NokiaPhoneModel)Notifier.CurrentModel; - ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); - } - else if ((Notifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) || (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash)) - { - FlashModel = (NokiaFlashModel)Notifier.CurrentModel; - Info = FlashModel.ReadPhoneInfo(); - ProductCode = Info.ProductCode; - } - else - { - NormalModel = (NokiaPhoneModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Normal); - ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); - } - URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); - DownloadFolder = args.Length >= 3 - ? args[4] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - Notifier.Stop(); - break; - case "downloadffubyoperatorcode": - LogFile.Log("Command: Download FFU by Operator Code", LogType.FileAndConsole); - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyOperatorCode "); - } - - ProductType = args[2]; - LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); - OperatorCode = args[3]; - LogFile.Log("Operator code: " + OperatorCode, LogType.FileAndConsole); - DownloadFolder = args.Length >= 5 - ? args[4] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - URL = LumiaDownloadModel.SearchFFU(ProductType, null, OperatorCode); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - break; - case "downloadffubyproductcode": - LogFile.Log("Command: Download FFU by Product Code", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyProductCode "); - } - - ProductCode = args[2]; - LogFile.Log("Product code: " + ProductCode, LogType.FileAndConsole); - URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); - DownloadFolder = args.Length >= 4 - ? args[3] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - break; - case "downloadffubyproducttype": - LogFile.Log("Command: Download FFU by Product Type", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyProductType "); - } - - ProductType = args[2]; - LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); - DownloadFolder = args.Length >= 4 - ? args[3] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - break; - case "searchffubyproducttype": - LogFile.Log("Command: Search FFU by Product Type", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -SearchFFUbyProductType "); - } - - ProductType = args[2]; - LogFile.Log("Lumia model: " + ProductType, LogType.FileAndConsole); - URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - break; - case "downloademergency": - LogFile.Log("Command: Download Emergency files", LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) - { - NormalModel = (NokiaPhoneModel)Notifier.CurrentModel; - ProductType = NormalModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); - if (ProductType.Contains('_')) - { - ProductType = ProductType.Substring(0, ProductType.IndexOf('_')); - } - } - else if ((Notifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) || (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash)) - { - FlashModel = (NokiaFlashModel)Notifier.CurrentModel; - Info = FlashModel.ReadPhoneInfo(); - ProductType = Info.Type; - } - else - { - NormalModel = (NokiaPhoneModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Normal); - ProductType = NormalModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); - if (ProductType.Contains('_')) - { - ProductType = ProductType.Substring(0, ProductType.IndexOf('_')); - } - } - URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - if (URLs != null) - { - DownloadFolder = args.Length >= 3 - ? args[2] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - for (int i = 0; i < URLs.Length; i++) - { - LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); - URI = new Uri(URLs[i]); - EmergencyFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); - EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); - if (i == 0) - { - ProgrammerPath = EmergencyFilePath; - } - else - { - PayloadPath = EmergencyFilePath; - } - - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - } - App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); - } - Notifier.Stop(); - break; - case "downloademergencybyproducttype": - LogFile.Log("Command: Download Emergency files", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new WPinternalsException("Wrong number of arguments. Usage: WPinternals.exe -DownloadEmergencyByProductType "); - } - - ProductType = args[2]; - URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - if (URLs != null) - { - DownloadFolder = args.Length >= 4 - ? args[3] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - for (int i = 0; i < URLs.Length; i++) - { - LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); - URI = new Uri(URLs[i]); - EmergencyFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); - EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); - if (i == 0) - { - ProgrammerPath = EmergencyFilePath; - } - else - { - PayloadPath = EmergencyFilePath; - } - - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - } - App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); - } - break; - case "downloadall": - LogFile.Log("Command: Download all", LogType.FileAndConsole); - Notifier = new PhoneNotifierViewModel(); - UIContext.Send(s => Notifier.Start(), null); - if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) - { - NormalModel = (NokiaPhoneModel)Notifier.CurrentModel; - ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); - } - else if ((Notifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) || (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash)) - { - FlashModel = (NokiaFlashModel)Notifier.CurrentModel; - Info = FlashModel.ReadPhoneInfo(); - ProductCode = Info.ProductCode; - } - else - { - NormalModel = (NokiaPhoneModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Normal); - ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); - } - URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); - DownloadFolder = args.Length >= 3 - ? args[2] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - if (URLs != null) - { - for (int i = 0; i < URLs.Length; i++) - { - LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); - URI = new Uri(URLs[i]); - EmergencyFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); - EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); - if (i == 0) - { - ProgrammerPath = EmergencyFilePath; - } - else - { - PayloadPath = EmergencyFilePath; - } - - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - } - App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); - } - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - ProductType = "RM-1085"; - - URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); - DownloadFolder = args.Length >= 3 - ? args[2] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); - } - } - Notifier.Stop(); - break; - case "downloadallbyproducttype": - LogFile.Log("Command: Download all by Product Type", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadAllByProductType "); - } - - ProductType = args[2]; - LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); - DownloadFolder = args.Length >= 4 - ? args[3] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - if (URLs != null) - { - for (int i = 0; i < URLs.Length; i++) - { - LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); - URI = new Uri(URLs[i]); - EmergencyFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); - EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); - if (i == 0) - { - ProgrammerPath = EmergencyFilePath; - } - else - { - PayloadPath = EmergencyFilePath; - } - - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - } - App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); - } - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - ProductType = "RM-1085"; - - URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); - DownloadFolder = args.Length >= 4 - ? args[3] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); - } - } - break; - case "downloadallbyproductcode": - LogFile.Log("Command: Download all by Product Code", LogType.FileAndConsole); - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadAllByProductCode "); - } - - ProductCode = args[2]; - LogFile.Log("Product code: " + ProductCode, LogType.FileAndConsole); - URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); - DownloadFolder = args.Length >= 4 - ? args[3] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - if (URLs != null) - { - for (int i = 0; i < URLs.Length; i++) - { - LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); - URI = new Uri(URLs[i]); - EmergencyFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); - EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); - if (i == 0) - { - ProgrammerPath = EmergencyFilePath; - } - else - { - PayloadPath = EmergencyFilePath; - } - - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - } - App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); - } - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - ProductType = "RM-1085"; - - URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); - DownloadFolder = args.Length >= 4 - ? args[3] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); - } - } - break; - case "downloadallbyoperatorcode": - LogFile.Log("Command: Download FFU by Operator Code", LogType.FileAndConsole); - if (args.Length < 4) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyOperatorCode "); - } - - ProductType = args[2]; - LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); - OperatorCode = args[3]; - LogFile.Log("Operator code: " + OperatorCode, LogType.FileAndConsole); - DownloadFolder = args.Length >= 5 - ? args[4] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - URL = LumiaDownloadModel.SearchFFU(ProductType, null, OperatorCode); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - if (URLs != null) - { - for (int i = 0; i < URLs.Length; i++) - { - LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); - URI = new Uri(URLs[i]); - EmergencyFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); - EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); - if (i == 0) - { - ProgrammerPath = EmergencyFilePath; - } - else - { - PayloadPath = EmergencyFilePath; - } - - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - } - App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); - } - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - ProductType = "RM-1085"; - - URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); - DownloadFolder = args.Length >= 5 - ? args[4] - : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); - - if (!Directory.Exists(DownloadFolder)) - { - Directory.CreateDirectory(DownloadFolder); - } - - LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); - LogFile.Log("URL: " + URL, LogType.FileAndConsole); - URI = new Uri(URL); - FFUFileName = Path.GetFileName(URI.LocalPath); - LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); - FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); - LogFile.Log("Downloading...", LogType.FileAndConsole); - using (System.Net.WebClient myWebClient = new()) - { - await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); - } - LogFile.Log("Download finished", LogType.FileAndConsole); - App.Config.AddFfuToRepository(FFUFilePath); - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); - } - } - break; - case "rewritepartitionsfrommassstorage": - if (args.Length < 2) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RewritePartitionsFromMassStorage \n The name of the imgs must be the partition names. For example, DPP.img will get written to the DPP partition."); - } - - await TestCode.RewriteParts(args[2]); - break; - case "restoregptusingedl": - if (args.Length < 3) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RestoreGPTUsingEDL "); - } - - await TestCode.RecoverBadGPT(args[2], args[3]); - break; - case "restoregptusingmassstorage": - if (args.Length < 2) - { - throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RestoreGPTUsingMassStorage "); - } - - await TestCode.RewriteGPT(args[2]); - break; - default: - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log("WPinternals commandline usage:", LogType.ConsoleOnly); - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log("WPinternals -ShowPhoneInfo", LogType.ConsoleOnly); - LogFile.Log("WPinternals -AddFFU ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -AddEmergency ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -RemoveFFU ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -RemoveEmergency ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -ListRepository", LogType.ConsoleOnly); - LogFile.Log("WPinternals -FindFlashingProfile ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -UnlockBootloader", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -FixBootAfterUnlockingBootloader", LogType.ConsoleOnly); - LogFile.Log("WPinternals -EnableRootAccess", LogType.ConsoleOnly); - LogFile.Log("WPinternals -UnlockBootLoaderOnImage ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -EnableRootAccessOnImage ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -UnlockBootLoaderOnMountedImage ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -EnableRootAccessOnMountedImage ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -EnableTestSigning", LogType.ConsoleOnly); - LogFile.Log("WPinternals -RelockPhone", LogType.ConsoleOnly); - LogFile.Log("WPinternals -SwitchToMassStorageMode", LogType.ConsoleOnly); - LogFile.Log("WPinternals -FlashFFU ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -FlashCustomROM ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -FlashPartition ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -FlashRaw ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -ClearNV", LogType.ConsoleOnly); - LogFile.Log("WPinternals -ReadGPT", LogType.ConsoleOnly); - LogFile.Log("WPinternals -BackupGPT ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -RestoreGPT ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -MergeGPT ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -MergeGPT ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -ShowFFU ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DumpFFU ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadFFU ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadFFUbyProductType ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadFFUbyProductCode ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadFFUbyOperatorCode ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadEmergency ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadEmergencyByProductType ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadAll ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadAllByProductType ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadAllByProductCode ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DownloadAllByOperatorCode ", LogType.ConsoleOnly); - LogFile.Log(" ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -DumpUEFI ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -TestProgrammer ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -RewritePartitionsFromMassStorage ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -RestoreGPTUsingEDL ", LogType.ConsoleOnly); - LogFile.Log("WPinternals -RestoreGPTUsingMassStorage ", LogType.ConsoleOnly); - break; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - - if (Environment.GetCommandLineArgs().Length > 1) - { - CloseConsole(); - } - } - - // http://stackoverflow.com/questions/472282/show-console-in-windows-application - // https://stackoverflow.com/questions/807998/how-do-i-create-a-c-sharp-app-that-decides-itself-whether-to-show-as-a-console-o - // http://www.csharp411.com/console-output-from-winforms-application/#comment-76 - // https://stackoverflow.com/questions/1305257/using-attachconsole-user-must-hit-enter-to-get-regular-command-line - internal static void OpenConsole() - { - if (IsConsoleVisible) - { - return; - } - - if (AttachConsole(-1)) - { - Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r"); // Prompt was already printed. Clear that line. - - /* - Other possibility to clear line: - - System.Console.CursorLeft = 0; - char[] bl = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Repeat(' ', System.Console.WindowWidth - 1)); - System.Console.Write(bl); - System.Console.CursorLeft = 0; - */ - - hConsoleWnd = GetForegroundWindow(); - } - else - { - AllocConsole(); - IsNewConsoleCreated = true; - - hConsoleWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; - - // Support VS 2017 debugger without VS host process: - try - { - // Console.OpenStandardOutput eventually calls into GetStdHandle. As per MSDN documentation of GetStdHandle: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231(v=vs.85).aspx will return the redirected handle and not the allocated console: - // "The standard handles of a process may be redirected by a call to SetStdHandle, in which case GetStdHandle returns the redirected handle. If the standard handles have been redirected, you can specify the CONIN$ value in a call to the CreateFile function to get a handle to a console's input buffer. Similarly, you can specify the CONOUT$ value to get a handle to a console's active screen buffer." - // Get the handle to CONOUT$. - IntPtr stdHandle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); - Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = new(stdHandle, true); - FileStream fileStream = new(safeFileHandle, FileAccess.Write); - Encoding encoding = Encoding.GetEncoding(MY_CODE_PAGE); - StreamWriter standardOutput = new(fileStream, encoding); - standardOutput.AutoFlush = true; - Console.SetOut(standardOutput); - } - catch - { - } - } - - IsConsoleVisible = true; - - Console.CancelKeyPress += Console_CancelKeyPress; - - LogFile.LogApplicationVersion(); - } - - private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) - { - LogFile.Log("Operation canceled!", LogType.FileOnly); - LogFile.EndAction(); - -#if PREVIEW - Uploader.WaitForUploads(); -#endif - } - - internal static void CloseConsole(bool Exit = true) - { - if (IsConsoleVisible) - { - if (IsNewConsoleCreated) - { - Console.WriteLine("Press any key to close..."); - Console.ReadKey(); - } - - PostMessage(hConsoleWnd, WM_KEYDOWN, VK_RETURN, 0); - - IsConsoleVisible = false; - - if (Exit) - { -#if PREVIEW - Uploader.WaitForUploads(); -#endif - - Environment.Exit(0); - } - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace WPinternals +{ + internal static class CommandLine + { + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AllocConsole(); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AttachConsole(int dwProcessId); + + [DllImport("kernel32.dll")] + private static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + private const UInt32 StdOutputHandle = 0xFFFFFFF5; + + [DllImport("kernel32.dll")] + private static extern IntPtr GetStdHandle(UInt32 nStdHandle); + + [DllImport("kernel32.dll")] + private static extern void SetStdHandle(UInt32 nStdHandle, IntPtr handle); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, uint lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, uint hTemplateFile); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + private const int SW_HIDE = 0; + private const int SW_SHOW = 5; + + private const int MY_CODE_PAGE = 437; + private const uint GENERIC_WRITE = 0x40000000; + private const uint FILE_SHARE_WRITE = 0x2; + private const uint OPEN_EXISTING = 0x3; + + internal static bool IsConsoleVisible = false; + internal static bool IsNewConsoleCreated = false; + + private static IntPtr hConsoleWnd; + + [DllImport("User32.Dll", EntryPoint = "PostMessageA")] + private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam); + + private const int VK_RETURN = 0x0D; + private const int WM_KEYDOWN = 0x100; + + /// + /// When the main window should not be shown, this function should call ExitProcess and it should not return. + /// + internal static async Task ParseCommandLine(System.Threading.SynchronizationContext UIContext) + { + FFU FFU = null; + PhoneNotifierViewModel Notifier; + NokiaFlashModel FlashModel; + NokiaPhoneModel NormalModel; + PhoneInfo Info; + string ProductType; + string ProductCode; + string OperatorCode; + string DownloadFolder; + string FFUFilePath; + string URL; + string[] URLs; + Uri URI; + string FFUFileName; + string EmergencyFileName; + string EmergencyFilePath; + string ProgrammerPath = ""; + string PayloadPath = ""; + string EfiEspImagePath = null; + string MainOsImagePath = null; + DiscUtils.Fat.FatFileSystem UnlockedEFIESPFileSystem; + DiscUtils.Ntfs.NtfsFileSystem UnlockedMainOsFileSystem; + bool PatchResult; + + try + { + string[] args = Environment.GetCommandLineArgs(); + + if (args.Length == 1) + { + return; + } + + switch (args[1].ToLower().TrimStart(new char[] { '-', '/' })) + { +#if DEBUG + case "test": + LogFile.BeginAction("Test"); + await TestCode.Test(UIContext); + LogFile.EndAction("Test"); + break; +#endif + case "flashpartition": + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashPartition "); + } + + if (args.Length >= 5) + { + await LumiaV2UnlockBootViewModel.LumiaV2FlashPartition(UIContext, args[4], args[2], args[3]); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2FlashPartition(UIContext, null, args[2], args[3]); + } + + break; + case "flashraw": + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashRaw "); + } + + UInt64 StartSector = 0; + try + { + StartSector = args[2].StartsWith("0x", StringComparison.OrdinalIgnoreCase) + ? Convert.ToUInt64(args[2][2..], 16) + : Convert.ToUInt64(args[2], 10); + } + catch + { + LogFile.Log("Bad start sector", LogType.ConsoleOnly); + break; + } + if (args.Length >= 5) + { + await LumiaV2UnlockBootViewModel.LumiaV2FlashRaw(UIContext, StartSector, args[3], args[4]); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2FlashRaw(UIContext, StartSector, args[3], null); + } + + break; + case "flashpartitionimmediately": + if (args.Length < 5) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashPartition "); + } + + await LumiaV2UnlockBootViewModel.LumiaV2FlashPartition(UIContext, args[4], args[2], args[3], false); + break; + case "readgpt": + LogFile.BeginAction("ReadGPT"); + try + { + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Bootloader); // This also works for Bootloader Spec A + + GPT GPT = FlashModel.ReadGPT(); // May throw NotSupportedException + foreach (Partition Partition in GPT.Partitions) + { + LogFile.Log(Partition.Name.PadRight(20) + "0x" + Partition.FirstSector.ToString("X8") + " - 0x" + Partition.LastSector.ToString("X8") + " " + Partition.Volume, LogType.ConsoleOnly); + } + + if (FlashModel.ReadPhoneInfo(false).FlashAppProtocolVersionMajor >= 2) + { + FlashModel.SwitchToFlashAppContext(); + } + else + { + await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + } + + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("ReadGPT"); + } + break; + case "backupgpt": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -BackupGPT "); + } + + LogFile.BeginAction("BackupGPT"); + try + { + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + GPT GPT = FlashModel.ReadGPT(); // May throw NotSupportedException + Directory.CreateDirectory(Path.GetDirectoryName(args[2])); + GPT.WritePartitions(args[2]); + FlashModel.SwitchToFlashAppContext(); + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("BackupGPT"); + } + break; + case "restoregpt": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RestoreGPT "); + } + + LogFile.BeginAction("RestoreGPT"); + try + { + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + byte[] GptChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); + GPT GPT = new(GptChunk); + string Xml = File.ReadAllText(args[2]); + GPT.MergePartitions(Xml, false); + GPT.Rebuild(); + await LumiaV2UnlockBootViewModel.LumiaV2CustomFlash(Notifier, null, false, false, 0, GptChunk, true, true); + FlashModel.SwitchToFlashAppContext(); + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("RestoreGPT"); + } + break; + case "mergegpt": + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -MergeGPT Or use: WPinternals.exe -MergeGPT "); + } + + LogFile.BeginAction("MergeGPT"); + try + { + GPT GPT = GPT.ReadPartitions(args[2]); + + ZipArchive Archive = null; + FileStream s = null; + try + { + s = new FileStream(args[3], FileMode.Open, FileAccess.Read); + Archive = new ZipArchive(s); + } + catch { } + + if (Archive == null) + { + s?.Close(); + + // Assume Xml-file + GPT.MergePartitionsFromFile(args[3], true); + } + else + { + ZipArchiveEntry PartitionEntry = Archive.GetEntry("Partitions.xml"); + if (PartitionEntry == null) + { + GPT.MergePartitions(null, true, Archive); + } + else + { + using Stream ZipStream = PartitionEntry.Open(); + using StreamReader ZipReader = new(ZipStream); + string PartitionXml = ZipReader.ReadToEnd(); + GPT.MergePartitions(PartitionXml, true, Archive); + } + } + + Archive?.Dispose(); + + if (args.Length >= 5) + { + GPT.WritePartitions(args[4]); + } + + foreach (Partition Partition in GPT.Partitions) + { + LogFile.Log(Partition.Name.PadRight(20) + "0x" + Partition.FirstSector.ToString("X8") + " - 0x" + Partition.LastSector.ToString("X8") + " " + Partition.Volume, LogType.ConsoleOnly); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("MergeGPT"); + } + break; + case "dumpffu": + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DumpFFU "); + } + + FFU = new FFU(args[2]); + if (args.Length < 5) + { + foreach (Partition Partition in FFU.GPT.Partitions) + { + if (FFU.IsPartitionPresentInFFU(Partition.Name)) + { + FFU.WritePartition(Partition.Name, Path.Combine(args[3], Partition.Name + ".bin")); + } + } + } + else + { + Partition Target = FFU.GPT.GetPartition(args[4]); + if ((Target == null) || (!FFU.IsPartitionPresentInFFU(Target.Name))) + { + throw new InvalidOperationException("Partition not found in FFU!"); + } + + FFU.WritePartition(Target.Name, Path.Combine(args[3], Target.Name + ".bin")); + } + break; + case "dumpuefi": + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DumpUEFI "); + } + + byte[] UefiBinary; + if (FFU.IsFFU(args[2])) + { + FFU = new FFU(args[2]); + UefiBinary = FFU.GetPartition("UEFI"); + } + else + { + UefiBinary = File.ReadAllBytes(args[2]); + } + + UEFI UEFI = new(UefiBinary); + + foreach (EFI Efi in UEFI.EFIs) + { + byte[] EfiBinary = UEFI.GetFile(Efi.Guid); + string Name = Efi.Name ?? Efi.Guid.ToString(); + + if (!Name.Contains('.')) + { + Name += Efi.Type switch + { + 5 or 7 => ".dll", + 9 => ".exe", + _ => ".bin", + }; + } + string EfiPath = Path.Combine(args[3], Name); + Directory.CreateDirectory(Path.GetDirectoryName(EfiPath)); + File.WriteAllBytes(EfiPath, EfiBinary); + } + break; + case "testprogrammer": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -TestProgrammer "); + } + + await TestCode.TestProgrammer(UIContext, args[2]); + break; + case "findflashingprofile": + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + if (args.Length > 2) + { + await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, args[2]); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, null); + } + + Notifier.Stop(); + break; + case "findflashingprofileexperimental": + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + if (args.Length > 2) + { + await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, args[2], Experimental: true); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, null, Experimental: true); + } + + Notifier.Stop(); + break; + case "findflashingprofilenorestart": + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + if (args.Length > 2) + { + await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, args[2], false); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2FindFlashingProfile(Notifier, null, false); + } + + Notifier.Stop(); + break; + case "enabletestsigning": + if (args.Length > 2) + { + await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, args[2]); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, null); + } + + break; + case "enabletestsigningnorestart": + if (args.Length > 2) + { + await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, args[2], false); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2EnableTestSigning(UIContext, null, false); + } + + break; + case "clearnv": + if (args.Length > 2) + { + await LumiaV2UnlockBootViewModel.LumiaV2ClearNV(UIContext, args[2]); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2ClearNV(UIContext, null); + } + + break; + case "switchtomassstoragemode": + LogFile.BeginAction("SwitchToMassStorageMode"); + try + { + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + LogFile.Log("Command: Switch to Mass Storage Mode", LogType.FileAndConsole); + if (args.Length > 2) + { + await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, args[2]); + } + else + { + await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, null); + } + + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("SwitchToMassStorageMode"); + } + break; + case "relockphone": + Notifier = new PhoneNotifierViewModel(); + try + { + UIContext.Send(s => Notifier.Start(), null); + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + Info = FlashModel.ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + + FFU ProfileFFU = null; + FFU CurrentFFU; + for (int i = 2; i <= 3; i++) + { + if (args.Length > i) + { + CurrentFFU = new FFU(args[i]); + string CurrentVersion = CurrentFFU.GetOSVersion(); + string PlatformID = CurrentFFU.PlatformID; + + // Check if the current FFU matches the connected phone, so that the FFU can be used for profiling. + if (Info.PlatformID.StartsWith(PlatformID, StringComparison.OrdinalIgnoreCase)) + { + ProfileFFU = CurrentFFU; + } + } + } + + if (ProfileFFU == null) + { + List FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); + ProfileFFU = FFUs.Count > 0 + ? new FFU(FFUs[0].Path) + : throw new WPinternalsException("Profile FFU missing", "No profile FFU has been found in the repository for your device. You can add a profile FFU within the download section of the tool or by using the command line."); + } + LogFile.Log("Profile FFU: " + ProfileFFU.Path); + + UIContext.Send(s => Notifier.Start(), null); + + await LumiaUnlockBootloaderViewModel.LumiaRelockUEFI(Notifier, ProfileFFU.Path); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + Notifier.Stop(); + break; + case "addffu": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -AddFFU "); + } + + App.Config.AddFfuToRepository(args[2]); + break; + case "removeffu": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RemoveFFU "); + } + + App.Config.RemoveFfuFromRepository(args[2]); + break; + case "addemergency": + if (args.Length < 5) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -AddEmergnency "); + } + + App.Config.AddEmergencyToRepository(args[2], args[3], args[4]); + break; + case "removeemergency": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RemoveEmergency "); + } + + App.Config.RemoveEmergencyFromRepository(args[2]); + break; + case "listrepository": + int Count = 0; + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log("FFU Repository:", LogType.ConsoleOnly); + foreach (FFUEntry Entry in App.Config.FFURepository) + { + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log("FFU " + Count.ToString() + ":", LogType.ConsoleOnly); + LogFile.Log("File: " + Entry.Path + (Entry.Exists() ? "" : " (file is missing)"), LogType.ConsoleOnly); + LogFile.Log("Platform ID: " + Entry.PlatformID, LogType.ConsoleOnly); + if (Entry.FirmwareVersion != null) + { + LogFile.Log("Firmware version: " + Entry.FirmwareVersion, LogType.ConsoleOnly); + } + + if (Entry.OSVersion != null) + { + LogFile.Log("OS version: " + Entry.OSVersion, LogType.ConsoleOnly); + } + + Count++; + } + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log("Emergency Repository:", LogType.ConsoleOnly); + Count = 0; + foreach (EmergencyFileEntry Entry in App.Config.EmergencyRepository) + { + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log("Emergency " + Count.ToString() + ":", LogType.ConsoleOnly); + LogFile.Log("Type: " + Entry.Type, LogType.ConsoleOnly); + LogFile.Log("Programmer file: " + Entry.ProgrammerPath + (Entry.ProgrammerExists() ? "" : " (file is missing)"), LogType.ConsoleOnly); + if (Entry.PayloadPath != null) + { + LogFile.Log("Payload file: " + Entry.PayloadPath + (Entry.PayloadExists() ? "" : " (file is missing)"), LogType.ConsoleOnly); + } + + Count++; + } + break; + case "showffu": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -ShowFFU "); + } + + string FFUPath = args[2]; + LogFile.Log("FFU: " + FFUPath, LogType.ConsoleOnly); + FFU = new FFU(FFUPath); + + // Show basics + LogFile.Log("Platform ID: " + FFU.PlatformID, LogType.ConsoleOnly); + string Firmware = FFU.GetFirmwareVersion(); + if (Firmware != null) + { + LogFile.Log("Firmware version: " + Firmware, LogType.ConsoleOnly); + } + + string OSVersion = FFU.GetOSVersion(); + if (OSVersion != null) + { + LogFile.Log("OS version: " + OSVersion, LogType.ConsoleOnly); + } + + // Show partitions from GPT (also show which partitions are in the FFU payload) + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log("Partition table:", LogType.ConsoleOnly); + LogFile.Log("Name".PadRight(20) + "Start-sector".PadRight(20) + "End-sector".PadRight(20) + "Present in FFU", LogType.ConsoleOnly); + foreach (Partition p in FFU.GPT.Partitions) + { + LogFile.Log(p.Name.PadRight(20) + ("0x" + p.FirstSector.ToString("X16")).PadRight(20) + ("0x" + p.LastSector.ToString("X16")).PadRight(20) + (FFU.IsPartitionPresentInFFU(p.Name) ? "Yes" : "No"), LogType.ConsoleOnly); + } + break; + case "showphoneinfo": + LogFile.Log("Command: Show phone info", LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + Info = FlashModel.ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + Notifier.Stop(); + break; + case "unlockbootloader": + LogFile.BeginAction("UnlockBootloader"); + try + { + LogFile.Log("Command: Unlock Bootloader", LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + Info = FlashModel.ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + + FFU ProfileFFU = null; + FFU SupportedFFU = null; + FFU CurrentFFU; + for (int i = 2; i <= 3; i++) + { + if (args.Length > i) + { + CurrentFFU = new FFU(args[i]); + string CurrentVersion = CurrentFFU.GetOSVersion(); + string PlatformID = CurrentFFU.PlatformID; + + // Check if the current FFU matches the connected phone, so that the FFU can be used for profiling. + if (Info.PlatformID.StartsWith(PlatformID, StringComparison.OrdinalIgnoreCase)) + { + ProfileFFU = CurrentFFU; + } + + // Check if the current FFU is supported for unlocking. + if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == CurrentVersion)) + { + SupportedFFU = CurrentFFU; + } + } + } + + if (ProfileFFU == null) + { + List FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); + ProfileFFU = FFUs.Count > 0 + ? new FFU(FFUs[0].Path) + : throw new WPinternalsException("Profile FFU missing", "No profile FFU has been found in the repository for your device. You can add a profile FFU within the download section of the tool or by using the command line."); + } + LogFile.Log("Profile FFU: " + ProfileFFU.Path); + + if (SupportedFFU == null) + { + List FFUs = App.Config.FFURepository.Where(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion)).ToList(); + SupportedFFU = FFUs.Count > 0 + ? new FFU(FFUs[0].Path) + : throw new WPinternalsException("No donor-FFU found with supported OS version", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); + } + + await LumiaUnlockBootloaderViewModel.LumiaUnlockUEFI(Notifier, ProfileFFU.Path, null, SupportedFFU.Path); + + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("UnlockBootloader"); + } + break; + case "flashcustomrom": + LogFile.BeginAction("FlashCustomROM"); + try + { + LogFile.Log("Command: Flash Custom ROM", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashCustomROM "); + } + + string CustomRomPath = args[2]; + LogFile.Log("Custom ROM: " + CustomRomPath, LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + Info = FlashModel.ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + LogFile.Log("Preparing to flash Custom ROM", LogType.FileAndConsole); + await LumiaV2UnlockBootViewModel.LumiaV2FlashArchive(Notifier, CustomRomPath); + LogFile.Log("Custom ROM flashed successfully", LogType.FileAndConsole); + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("FlashCustomROM"); + } + break; + case "flashffu": + LogFile.BeginAction("FlashFFU"); + try + { + LogFile.Log("Command: Flash FFU", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -FlashFFU "); + } + + FFUPath = args[2]; + LogFile.Log("FFU file: " + FFUPath, LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + Info = FlashModel.ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + LogFile.Log("Flashing FFU...", LogType.FileAndConsole); + await Task.Run(() => FlashModel.FlashFFU(new FFU(FFUPath), true, (byte)(!Info.IsBootloaderSecure ? FlashOptions.SkipSignatureCheck : 0))); + LogFile.Log("FFU flashed successfully", LogType.FileAndConsole); + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("FlashFFU"); + } + break; + case "fixbootafterunlockingbootloader": + LogFile.BeginAction("FixBoot"); + try + { + LogFile.Log("Command: Fix boot after unlocking bootloader", LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + string Drive = await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, null); + Notifier.Stop(); + App.PatchEngine.TargetPath = Drive + "\\"; + PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the MainOS partition of your phone. Make sure your phone runs a supported Operating System version."); + } + + LogFile.Log("Fixed bootloader", LogType.FileAndConsole); + LogFile.Log("The phone is left in Mass Storage mode", LogType.FileAndConsole); + LogFile.Log("Press and hold the power-button of the phone for at least 10 seconds to reset the phone", LogType.FileAndConsole); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("FixBoot"); + } + break; + case "enablerootaccess": + LogFile.BeginAction("EnableRootAccess"); + try + { + LogFile.Log("Command: Enable root access on the phone", LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + string Drive = await LumiaV2UnlockBootViewModel.LumiaV2SwitchToMassStorageMode(Notifier, null); + Notifier.Stop(); + App.PatchEngine.TargetPath = Drive + "\\EFIESP\\"; + PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); + if (!PatchResult) + { + throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the EFIESP partition of your phone. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported."); + } + + App.PatchEngine.TargetPath = Drive + "\\"; + PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the MainOS partition of your phone. Make sure your phone runs a supported Operating System version."); + } + + PatchResult = App.PatchEngine.Patch("RootAccess-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Patch failed", "An error occured while modifying Operating System files on the MainOS partition of your phone for Root Access. Make sure your phone runs a supported Operating System version."); + } + + LogFile.Log("Root Access enabled!", LogType.FileAndConsole); + LogFile.Log("The phone is left in Mass Storage mode", LogType.FileAndConsole); + LogFile.Log("Press and hold the power-button of the phone for at least 10 seconds to reset the phone", LogType.FileAndConsole); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("EnableRootAccess"); + } + break; + case "unlockbootloaderonimage": + LogFile.Log("Command: Unlock bootloader on image", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -UnlockBootLoaderOnImage "); + } + + FFUFilePath = null; + EfiEspImagePath = args[2]; + if (args.Length > 3) + { + if (FFU.IsFFU(args[3])) + { + FFUFilePath = args[3]; + } + else + { + MainOsImagePath = args[3]; + } + } + if (args.Length > 4) + { + FFUFilePath = args[4]; + } + + using (FileStream FileSystemStream = new(EfiEspImagePath, FileMode.Open, FileAccess.ReadWrite)) + { + UnlockedEFIESPFileSystem = new DiscUtils.Fat.FatFileSystem(FileSystemStream); + + if (FFUFilePath != null) + { + FFU SupportedFFU = new(FFUFilePath); + LogFile.Log("Donor FFU: " + SupportedFFU.Path); + byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); + DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); + DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); + MemoryStream SupportedMobileStartupMemStream = new(); + SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); + byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); + SupportedMobileStartupMemStream.Close(); + SupportedMobileStartupStream.Close(); + + // Save supported mobilestartup.efi + LogFile.Log("Taking mobilestartup.efi from donor-FFU"); + Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write); + MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); + MobileStartupStream.Close(); + } + + App.PatchEngine.TargetImage = UnlockedEFIESPFileSystem; + PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); + } + + // Edit BCD + LogFile.Log("Edit BCD"); + using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"\efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite); + using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); + DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); + DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); + DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + + DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); + NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + } + + if (MainOsImagePath != null) + { + using FileStream FileSystemStream = new(MainOsImagePath, FileMode.Open, FileAccess.ReadWrite); + UnlockedMainOsFileSystem = new DiscUtils.Ntfs.NtfsFileSystem(FileSystemStream); + + App.PatchEngine.TargetImage = UnlockedMainOsFileSystem; + PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); + } + } + LogFile.Log("Bootloader unlocked on image", LogType.FileAndConsole); + break; + case "enablerootaccessonimage": + LogFile.Log("Command: Enable root access on image", LogType.FileAndConsole); + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -EnableRootAccessOnImage "); + } + + FFUFilePath = null; + EfiEspImagePath = args[2]; + MainOsImagePath = args[3]; + if (args.Length > 4) + { + FFUFilePath = args[4]; + } + + using (FileStream FileSystemStream = new(EfiEspImagePath, FileMode.Open, FileAccess.ReadWrite)) + { + UnlockedEFIESPFileSystem = new DiscUtils.Fat.FatFileSystem(FileSystemStream); + + if (FFUFilePath != null) + { + FFU SupportedFFU = new(FFUFilePath); + LogFile.Log("Supported FFU: " + SupportedFFU.Path); + byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); + DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); + DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); + MemoryStream SupportedMobileStartupMemStream = new(); + SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); + byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); + SupportedMobileStartupMemStream.Close(); + SupportedMobileStartupStream.Close(); + + // Save supported mobilestartup.efi + LogFile.Log("Taking mobilestartup.efi from donor-FFU"); + Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write); + MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); + MobileStartupStream.Close(); + } + + App.PatchEngine.TargetImage = UnlockedEFIESPFileSystem; + PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); + } + + // Edit BCD + LogFile.Log("Edit BCD"); + using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"\efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite); + using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); + DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); + DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); + DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + + DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); + NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + } + + using (FileStream FileSystemStream = new(MainOsImagePath, FileMode.Open, FileAccess.ReadWrite)) + { + UnlockedMainOsFileSystem = new DiscUtils.Ntfs.NtfsFileSystem(FileSystemStream); + + App.PatchEngine.TargetImage = UnlockedMainOsFileSystem; + PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); + } + + PatchResult = App.PatchEngine.Patch("RootAccess-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch MainOS", "An error occured while modifying Operating System files on the MainOS partition you provided for Root Access. Make sure your phone runs a supported Operating System version."); + } + } + LogFile.Log("Root access enabled on image", LogType.FileAndConsole); + break; + case "unlockbootloaderonmountedimage": + LogFile.Log("Command: Unlock bootloader on mounted image", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -UnlockBootLoaderOnMountedImage "); + } + + FFUFilePath = null; + EfiEspImagePath = args[2]; + if (args.Length > 3) + { + if (FFU.IsFFU(args[3])) + { + FFUFilePath = args[3]; + } + else + { + MainOsImagePath = args[3]; + } + } + if (args.Length > 4) + { + FFUFilePath = args[4]; + } + + if (FFUFilePath != null) + { + FFU SupportedFFU = new(FFUFilePath); + LogFile.Log("Donor-FFU: " + SupportedFFU.Path); + byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); + DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); + DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); + MemoryStream SupportedMobileStartupMemStream = new(); + SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); + byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); + SupportedMobileStartupMemStream.Close(); + SupportedMobileStartupStream.Close(); + + // Save supported mobilestartup.efi + LogFile.Log("Taking mobilestartup.efi from donor-FFU"); + Stream MobileStartupStream = File.Open(Path.Combine(EfiEspImagePath, @"Windows\System32\Boot\mobilestartup.efi"), FileMode.Create, FileAccess.Write); + MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); + MobileStartupStream.Close(); + } + + App.PatchEngine.TargetPath = EfiEspImagePath; + PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); + } + + // Edit BCD + LogFile.Log("Edit BCD"); + using (Stream BCDFileStream = File.Open(Path.Combine(EfiEspImagePath, @"efi\Microsoft\Boot\BCD"), FileMode.Open, FileAccess.ReadWrite)) + { + using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); + DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); + DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); + DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + + DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); + NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + } + + if (MainOsImagePath != null) + { + App.PatchEngine.TargetPath = MainOsImagePath; + PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); + } + } + LogFile.Log("Bootloader unlocked on image", LogType.FileAndConsole); + break; + case "enablerootaccessonmountedimage": + LogFile.Log("Command: Enable root access on mounted image", LogType.FileAndConsole); + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -EnableRootAccessOnMountedImage "); + } + + FFUFilePath = null; + EfiEspImagePath = args[2]; + MainOsImagePath = args[3]; + if (args.Length > 4) + { + FFUFilePath = args[4]; + } + + if (FFUFilePath != null) + { + FFU SupportedFFU = new(FFUFilePath); + LogFile.Log("Supported FFU: " + SupportedFFU.Path); + byte[] SupportedEFIESP = SupportedFFU.GetPartition("EFIESP"); + DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)); + DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open); + MemoryStream SupportedMobileStartupMemStream = new(); + SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream); + byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray(); + SupportedMobileStartupMemStream.Close(); + SupportedMobileStartupStream.Close(); + + // Save supported mobilestartup.efi + LogFile.Log("Taking mobilestartup.efi from donor-FFU"); + Stream MobileStartupStream = File.Open(Path.Combine(EfiEspImagePath, @"Windows\System32\Boot\mobilestartup.efi"), FileMode.Create, FileAccess.Write); + MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length); + MobileStartupStream.Close(); + } + + App.PatchEngine.TargetPath = EfiEspImagePath; + PatchResult = App.PatchEngine.Patch("SecureBootHack-V2-EFIESP"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch bootloader", "An error occured while patching Operating System files on the EFIESP partition provided. Make sure no boot files have been tampered with and you use the latest version of the tool. This error cannot be caused by an incorrect Operating System version as the tool automatically uses replacement if the version isn't supported, unless the replacement files have been tampered with or are not compatible."); + } + + // Edit BCD + LogFile.Log("Edit BCD"); + using (Stream BCDFileStream = File.Open(Path.Combine(EfiEspImagePath, @"efi\Microsoft\Boot\BCD"), FileMode.Open, FileAccess.ReadWrite)) + { + using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream); + DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root); + DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}")); + DiscUtils.BootConfig.Element NoCodeIntegrityElement = MobileStartupObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + MobileStartupObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + + DiscUtils.BootConfig.BcdObject WinLoadObject = BCDStore.GetObject(new Guid("{7619dcc9-fafe-11d9-b411-000476eba25f}")); + NoCodeIntegrityElement = WinLoadObject.GetElement(0x16000048); + if (NoCodeIntegrityElement != null) + { + NoCodeIntegrityElement.Value = DiscUtils.BootConfig.ElementValue.ForBoolean(true); + } + else + { + WinLoadObject.AddElement(0x16000048, DiscUtils.BootConfig.ElementValue.ForBoolean(true)); + } + } + + App.PatchEngine.TargetPath = MainOsImagePath; + PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch MainOS", "An error occured while patching Operating System files on the MainOS partition you provided. Make sure your phone runs a supported Operating System version."); + } + + PatchResult = App.PatchEngine.Patch("RootAccess-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Failed to patch MainOS", "An error occured while modifying Operating System files on the MainOS partition you provided for Root Access. Make sure your phone runs a supported Operating System version."); + } + + LogFile.Log("Root access enabled on image", LogType.FileAndConsole); + break; + case "downloadffu": + LogFile.Log("Command: Download FFU", LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) + { + NormalModel = (NokiaPhoneModel)Notifier.CurrentModel; + ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); + } + else if ((Notifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) || (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash)) + { + FlashModel = (NokiaFlashModel)Notifier.CurrentModel; + Info = FlashModel.ReadPhoneInfo(); + ProductCode = Info.ProductCode; + } + else + { + NormalModel = (NokiaPhoneModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Normal); + ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); + } + URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); + DownloadFolder = args.Length >= 3 + ? args[4] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + Notifier.Stop(); + break; + case "downloadffubyoperatorcode": + LogFile.Log("Command: Download FFU by Operator Code", LogType.FileAndConsole); + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyOperatorCode "); + } + + ProductType = args[2]; + LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); + OperatorCode = args[3]; + LogFile.Log("Operator code: " + OperatorCode, LogType.FileAndConsole); + DownloadFolder = args.Length >= 5 + ? args[4] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + URL = LumiaDownloadModel.SearchFFU(ProductType, null, OperatorCode); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + break; + case "downloadffubyproductcode": + LogFile.Log("Command: Download FFU by Product Code", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyProductCode "); + } + + ProductCode = args[2]; + LogFile.Log("Product code: " + ProductCode, LogType.FileAndConsole); + URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); + DownloadFolder = args.Length >= 4 + ? args[3] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + break; + case "downloadffubyproducttype": + LogFile.Log("Command: Download FFU by Product Type", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyProductType "); + } + + ProductType = args[2]; + LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); + DownloadFolder = args.Length >= 4 + ? args[3] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + break; + case "searchffubyproducttype": + LogFile.Log("Command: Search FFU by Product Type", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -SearchFFUbyProductType "); + } + + ProductType = args[2]; + LogFile.Log("Lumia model: " + ProductType, LogType.FileAndConsole); + URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + break; + case "downloademergency": + LogFile.Log("Command: Download Emergency files", LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) + { + NormalModel = (NokiaPhoneModel)Notifier.CurrentModel; + ProductType = NormalModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); + if (ProductType.Contains('_')) + { + ProductType = ProductType.Substring(0, ProductType.IndexOf('_')); + } + } + else if ((Notifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) || (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash)) + { + FlashModel = (NokiaFlashModel)Notifier.CurrentModel; + Info = FlashModel.ReadPhoneInfo(); + ProductType = Info.Type; + } + else + { + NormalModel = (NokiaPhoneModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Normal); + ProductType = NormalModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); + if (ProductType.Contains('_')) + { + ProductType = ProductType.Substring(0, ProductType.IndexOf('_')); + } + } + URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + if (URLs != null) + { + DownloadFolder = args.Length >= 3 + ? args[2] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + for (int i = 0; i < URLs.Length; i++) + { + LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); + URI = new Uri(URLs[i]); + EmergencyFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); + EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); + if (i == 0) + { + ProgrammerPath = EmergencyFilePath; + } + else + { + PayloadPath = EmergencyFilePath; + } + + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + } + App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); + } + Notifier.Stop(); + break; + case "downloademergencybyproducttype": + LogFile.Log("Command: Download Emergency files", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new WPinternalsException("Wrong number of arguments. Usage: WPinternals.exe -DownloadEmergencyByProductType "); + } + + ProductType = args[2]; + URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + if (URLs != null) + { + DownloadFolder = args.Length >= 4 + ? args[3] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + for (int i = 0; i < URLs.Length; i++) + { + LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); + URI = new Uri(URLs[i]); + EmergencyFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); + EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); + if (i == 0) + { + ProgrammerPath = EmergencyFilePath; + } + else + { + PayloadPath = EmergencyFilePath; + } + + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + } + App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); + } + break; + case "downloadall": + LogFile.Log("Command: Download all", LogType.FileAndConsole); + Notifier = new PhoneNotifierViewModel(); + UIContext.Send(s => Notifier.Start(), null); + if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) + { + NormalModel = (NokiaPhoneModel)Notifier.CurrentModel; + ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); + } + else if ((Notifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) || (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash)) + { + FlashModel = (NokiaFlashModel)Notifier.CurrentModel; + Info = FlashModel.ReadPhoneInfo(); + ProductCode = Info.ProductCode; + } + else + { + NormalModel = (NokiaPhoneModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Normal); + ProductCode = NormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); + } + URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); + DownloadFolder = args.Length >= 3 + ? args[2] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + if (URLs != null) + { + for (int i = 0; i < URLs.Length; i++) + { + LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); + URI = new Uri(URLs[i]); + EmergencyFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); + EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); + if (i == 0) + { + ProgrammerPath = EmergencyFilePath; + } + else + { + PayloadPath = EmergencyFilePath; + } + + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + } + App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); + } + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + ProductType = "RM-1085"; + + URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); + DownloadFolder = args.Length >= 3 + ? args[2] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); + } + } + Notifier.Stop(); + break; + case "downloadallbyproducttype": + LogFile.Log("Command: Download all by Product Type", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadAllByProductType "); + } + + ProductType = args[2]; + LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); + DownloadFolder = args.Length >= 4 + ? args[3] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + if (URLs != null) + { + for (int i = 0; i < URLs.Length; i++) + { + LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); + URI = new Uri(URLs[i]); + EmergencyFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); + EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); + if (i == 0) + { + ProgrammerPath = EmergencyFilePath; + } + else + { + PayloadPath = EmergencyFilePath; + } + + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + } + App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); + } + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + ProductType = "RM-1085"; + + URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); + DownloadFolder = args.Length >= 4 + ? args[3] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); + } + } + break; + case "downloadallbyproductcode": + LogFile.Log("Command: Download all by Product Code", LogType.FileAndConsole); + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadAllByProductCode "); + } + + ProductCode = args[2]; + LogFile.Log("Product code: " + ProductCode, LogType.FileAndConsole); + URL = LumiaDownloadModel.SearchFFU(null, ProductCode, null, out ProductType); + DownloadFolder = args.Length >= 4 + ? args[3] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + if (URLs != null) + { + for (int i = 0; i < URLs.Length; i++) + { + LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); + URI = new Uri(URLs[i]); + EmergencyFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); + EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); + if (i == 0) + { + ProgrammerPath = EmergencyFilePath; + } + else + { + PayloadPath = EmergencyFilePath; + } + + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + } + App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); + } + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + ProductType = "RM-1085"; + + URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); + DownloadFolder = args.Length >= 4 + ? args[3] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); + } + } + break; + case "downloadallbyoperatorcode": + LogFile.Log("Command: Download FFU by Operator Code", LogType.FileAndConsole); + if (args.Length < 4) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -DownloadFFUbyOperatorCode "); + } + + ProductType = args[2]; + LogFile.Log("Product type: " + ProductType, LogType.FileAndConsole); + OperatorCode = args[3]; + LogFile.Log("Operator code: " + OperatorCode, LogType.FileAndConsole); + DownloadFolder = args.Length >= 5 + ? args[4] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + URL = LumiaDownloadModel.SearchFFU(ProductType, null, OperatorCode); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + URLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + if (URLs != null) + { + for (int i = 0; i < URLs.Length; i++) + { + LogFile.Log("URL: " + URLs[i], LogType.FileAndConsole); + URI = new Uri(URLs[i]); + EmergencyFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + EmergencyFileName, LogType.FileAndConsole); + EmergencyFilePath = Path.Combine(DownloadFolder, EmergencyFileName); + if (i == 0) + { + ProgrammerPath = EmergencyFilePath; + } + else + { + PayloadPath = EmergencyFilePath; + } + + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URLs[i], EmergencyFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + } + App.Config.AddEmergencyToRepository(ProductType, ProgrammerPath, PayloadPath); + } + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + ProductType = "RM-1085"; + + URL = LumiaDownloadModel.SearchFFU(ProductType, null, null); + DownloadFolder = args.Length >= 5 + ? args[4] + : Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\Repository\\" + ProductType.ToUpper()); + + if (!Directory.Exists(DownloadFolder)) + { + Directory.CreateDirectory(DownloadFolder); + } + + LogFile.Log("Download folder: " + DownloadFolder, LogType.FileAndConsole); + LogFile.Log("URL: " + URL, LogType.FileAndConsole); + URI = new Uri(URL); + FFUFileName = Path.GetFileName(URI.LocalPath); + LogFile.Log("File: " + FFUFileName, LogType.FileAndConsole); + FFUFilePath = Path.Combine(DownloadFolder, FFUFileName); + LogFile.Log("Downloading...", LogType.FileAndConsole); + using (System.Net.WebClient myWebClient = new()) + { + await myWebClient.DownloadFileTaskAsync(URL, FFUFilePath); + } + LogFile.Log("Download finished", LogType.FileAndConsole); + App.Config.AddFfuToRepository(FFUFilePath); + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + throw new WPinternalsException("Unable to find compatible FFU", "No donor-FFU has been found in the repository with a supported OS version. You can add a donor-FFU within the download section of the tool or by using the command line. A donor-FFU can be for a different device and a different CPU than your device. It is only used to gather Operating System specific binaries to be patched and used as part of the unlock process."); + } + } + break; + case "rewritepartitionsfrommassstorage": + if (args.Length < 2) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RewritePartitionsFromMassStorage \n The name of the imgs must be the partition names. For example, DPP.img will get written to the DPP partition."); + } + + await TestCode.RewriteParts(args[2]); + break; + case "restoregptusingedl": + if (args.Length < 3) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RestoreGPTUsingEDL "); + } + + await TestCode.RecoverBadGPT(args[2], args[3]); + break; + case "restoregptusingmassstorage": + if (args.Length < 2) + { + throw new ArgumentException("Wrong number of arguments. Usage: WPinternals.exe -RestoreGPTUsingMassStorage "); + } + + await TestCode.RewriteGPT(args[2]); + break; + default: + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log("WPinternals commandline usage:", LogType.ConsoleOnly); + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log("WPinternals -ShowPhoneInfo", LogType.ConsoleOnly); + LogFile.Log("WPinternals -AddFFU ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -AddEmergency ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -RemoveFFU ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -RemoveEmergency ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -ListRepository", LogType.ConsoleOnly); + LogFile.Log("WPinternals -FindFlashingProfile ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -UnlockBootloader", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -FixBootAfterUnlockingBootloader", LogType.ConsoleOnly); + LogFile.Log("WPinternals -EnableRootAccess", LogType.ConsoleOnly); + LogFile.Log("WPinternals -UnlockBootLoaderOnImage ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -EnableRootAccessOnImage ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -UnlockBootLoaderOnMountedImage ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -EnableRootAccessOnMountedImage ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -EnableTestSigning", LogType.ConsoleOnly); + LogFile.Log("WPinternals -RelockPhone", LogType.ConsoleOnly); + LogFile.Log("WPinternals -SwitchToMassStorageMode", LogType.ConsoleOnly); + LogFile.Log("WPinternals -FlashFFU ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -FlashCustomROM ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -FlashPartition ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -FlashRaw ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -ClearNV", LogType.ConsoleOnly); + LogFile.Log("WPinternals -ReadGPT", LogType.ConsoleOnly); + LogFile.Log("WPinternals -BackupGPT ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -RestoreGPT ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -MergeGPT ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -MergeGPT ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -ShowFFU ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DumpFFU ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadFFU ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadFFUbyProductType ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadFFUbyProductCode ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadFFUbyOperatorCode ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadEmergency ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadEmergencyByProductType ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadAll ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadAllByProductType ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadAllByProductCode ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DownloadAllByOperatorCode ", LogType.ConsoleOnly); + LogFile.Log(" ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -DumpUEFI ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -TestProgrammer ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -RewritePartitionsFromMassStorage ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -RestoreGPTUsingEDL ", LogType.ConsoleOnly); + LogFile.Log("WPinternals -RestoreGPTUsingMassStorage ", LogType.ConsoleOnly); + break; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + + if (Environment.GetCommandLineArgs().Length > 1) + { + CloseConsole(); + } + } + + // http://stackoverflow.com/questions/472282/show-console-in-windows-application + // https://stackoverflow.com/questions/807998/how-do-i-create-a-c-sharp-app-that-decides-itself-whether-to-show-as-a-console-o + // http://www.csharp411.com/console-output-from-winforms-application/#comment-76 + // https://stackoverflow.com/questions/1305257/using-attachconsole-user-must-hit-enter-to-get-regular-command-line + internal static void OpenConsole() + { + if (IsConsoleVisible) + { + return; + } + + if (AttachConsole(-1)) + { + Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r"); // Prompt was already printed. Clear that line. + + /* + Other possibility to clear line: + + System.Console.CursorLeft = 0; + char[] bl = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Repeat(' ', System.Console.WindowWidth - 1)); + System.Console.Write(bl); + System.Console.CursorLeft = 0; + */ + + hConsoleWnd = GetForegroundWindow(); + } + else + { + AllocConsole(); + IsNewConsoleCreated = true; + + hConsoleWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; + + // Support VS 2017 debugger without VS host process: + try + { + // Console.OpenStandardOutput eventually calls into GetStdHandle. As per MSDN documentation of GetStdHandle: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231(v=vs.85).aspx will return the redirected handle and not the allocated console: + // "The standard handles of a process may be redirected by a call to SetStdHandle, in which case GetStdHandle returns the redirected handle. If the standard handles have been redirected, you can specify the CONIN$ value in a call to the CreateFile function to get a handle to a console's input buffer. Similarly, you can specify the CONOUT$ value to get a handle to a console's active screen buffer." + // Get the handle to CONOUT$. + IntPtr stdHandle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = new(stdHandle, true); + FileStream fileStream = new(safeFileHandle, FileAccess.Write); + Encoding encoding = Encoding.GetEncoding(MY_CODE_PAGE); + StreamWriter standardOutput = new(fileStream, encoding); + standardOutput.AutoFlush = true; + Console.SetOut(standardOutput); + } + catch + { + } + } + + IsConsoleVisible = true; + + Console.CancelKeyPress += Console_CancelKeyPress; + + LogFile.LogApplicationVersion(); + } + + private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) + { + LogFile.Log("Operation canceled!", LogType.FileOnly); + LogFile.EndAction(); + +#if PREVIEW + Uploader.WaitForUploads(); +#endif + } + + internal static void CloseConsole(bool Exit = true) + { + if (IsConsoleVisible) + { + if (IsNewConsoleCreated) + { + Console.WriteLine("Press any key to close..."); + Console.ReadKey(); + } + + PostMessage(hConsoleWnd, WM_KEYDOWN, VK_RETURN, 0); + + IsConsoleVisible = false; + + if (Exit) + { +#if PREVIEW + Uploader.WaitForUploads(); +#endif + + Environment.Exit(0); + } + } + } + } +} diff --git a/DiscUtils/DiscUtils.Core/CoreCompat/EncodingHelper.cs b/WPinternals/DiscUtils/DiscUtils.Core/CoreCompat/EncodingHelper.cs similarity index 100% rename from DiscUtils/DiscUtils.Core/CoreCompat/EncodingHelper.cs rename to WPinternals/DiscUtils/DiscUtils.Core/CoreCompat/EncodingHelper.cs diff --git a/DiscUtils/DiscUtils.Core/Internal/Utilities.cs b/WPinternals/DiscUtils/DiscUtils.Core/Internal/Utilities.cs similarity index 100% rename from DiscUtils/DiscUtils.Core/Internal/Utilities.cs rename to WPinternals/DiscUtils/DiscUtils.Core/Internal/Utilities.cs diff --git a/DiscUtils/DiscUtils.Fat/ClusterReader.cs b/WPinternals/DiscUtils/DiscUtils.Fat/ClusterReader.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/ClusterReader.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/ClusterReader.cs diff --git a/DiscUtils/DiscUtils.Fat/ClusterStream.cs b/WPinternals/DiscUtils/DiscUtils.Fat/ClusterStream.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/ClusterStream.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/ClusterStream.cs diff --git a/DiscUtils/DiscUtils.Fat/DirectoryEntry.cs b/WPinternals/DiscUtils/DiscUtils.Fat/DirectoryEntry.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/DirectoryEntry.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/DirectoryEntry.cs diff --git a/DiscUtils/DiscUtils.Fat/FatAttributes.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FatAttributes.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FatAttributes.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FatAttributes.cs diff --git a/DiscUtils/DiscUtils.Fat/FatBuffer.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FatBuffer.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FatBuffer.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FatBuffer.cs diff --git a/DiscUtils/DiscUtils.Fat/FatFileStream.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FatFileStream.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FatFileStream.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FatFileStream.cs diff --git a/DiscUtils/DiscUtils.Fat/FatFileSystemOptions.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FatFileSystemOptions.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FatFileSystemOptions.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FatFileSystemOptions.cs diff --git a/DiscUtils/DiscUtils.Fat/FatType.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FatType.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FatType.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FatType.cs diff --git a/DiscUtils/DiscUtils.Fat/FileAllocationTable.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FileAllocationTable.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FileAllocationTable.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FileAllocationTable.cs diff --git a/DiscUtils/DiscUtils.Fat/FileName.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FileName.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FileName.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FileName.cs diff --git a/DiscUtils/DiscUtils.Fat/FileSystemFactory.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FileSystemFactory.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FileSystemFactory.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FileSystemFactory.cs diff --git a/DiscUtils/DiscUtils.Fat/FirstClusterChangedDelegate.cs b/WPinternals/DiscUtils/DiscUtils.Fat/FirstClusterChangedDelegate.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/FirstClusterChangedDelegate.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/FirstClusterChangedDelegate.cs diff --git a/DiscUtils/DiscUtils.Fat/Modified/Directory.cs b/WPinternals/DiscUtils/DiscUtils.Fat/Modified/Directory.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/Modified/Directory.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/Modified/Directory.cs diff --git a/DiscUtils/DiscUtils.Fat/Modified/FatFileSystem.cs b/WPinternals/DiscUtils/DiscUtils.Fat/Modified/FatFileSystem.cs similarity index 100% rename from DiscUtils/DiscUtils.Fat/Modified/FatFileSystem.cs rename to WPinternals/DiscUtils/DiscUtils.Fat/Modified/FatFileSystem.cs diff --git a/FilePickerControl.xaml b/WPinternals/FilePickerControl.xaml similarity index 98% rename from FilePickerControl.xaml rename to WPinternals/FilePickerControl.xaml index 3a2fd33..6163421 100644 --- a/FilePickerControl.xaml +++ b/WPinternals/FilePickerControl.xaml @@ -1,41 +1,41 @@ - - - - - - - - Change - Clear - - + + + + + + + + Change + Clear + + diff --git a/FilePickerControl.xaml.cs b/WPinternals/FilePickerControl.xaml.cs similarity index 96% rename from FilePickerControl.xaml.cs rename to WPinternals/FilePickerControl.xaml.cs index e7d8716..bf532c4 100644 --- a/FilePickerControl.xaml.cs +++ b/WPinternals/FilePickerControl.xaml.cs @@ -1,525 +1,525 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.ComponentModel; -using System.Globalization; -using System.Threading; -using System.Windows; -using System.Windows.Media; - -namespace WPinternals -{ - public class PathChangedEventArgs : EventArgs - { - public PathChangedEventArgs(string NewPath) - : base() - { - this.NewPath = NewPath; - } - - public string NewPath; - } - - public delegate void PathChangedEventHandler( - Object sender, - PathChangedEventArgs e - ); - - /// - /// Interaction logic for FilePickerControl.xaml - /// - public partial class FilePickerBase : System.Windows.Controls.UserControl, INotifyPropertyChanged - { - private readonly SynchronizationContext UIContext; - - public event PropertyChangedEventHandler PropertyChanged = delegate { }; - public event EventHandler PathChanged = delegate { }; - - protected void OnPropertyChanged(string propertyName) - { - if (this.PropertyChanged != null) - { - if (SynchronizationContext.Current == UIContext) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } - else - { - UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); - } - } - } - - public FilePickerBase() - { - UIContext = SynchronizationContext.Current; - InitializeComponent(); - } - - public bool AllowNull - { - get - { - return (bool)GetValue(AllowNullProperty); - } - set - { - SetValue(AllowNullProperty, value); - Resize(); - } - } - - public static readonly DependencyProperty AllowNullProperty = - DependencyProperty.Register("AllowNull", typeof(bool), typeof(FilePickerBase), new UIPropertyMetadata(false)); - - public string Caption - { - get - { - return (string)GetValue(CaptionProperty); - } - set - { - SetValue(CaptionProperty, value); - Resize(); - } - } - - public static readonly DependencyProperty CaptionProperty = - DependencyProperty.Register("Caption", typeof(string), typeof(FilePickerBase), new UIPropertyMetadata(null)); - - public string Path - { - get - { - return (string)GetValue(PathProperty); - } - set - { - if ((string)GetValue(PathProperty) != value) - { - SetValue(PathProperty, value); - Resize(); - PathChanged(this, new PathChangedEventArgs(value)); - } - } - } - - public static readonly DependencyProperty PathProperty = - DependencyProperty.Register("Path", typeof(string), typeof(FilePickerBase), new UIPropertyMetadata(null)); - - public string SelectionText - { - get - { - return (string)GetValue(SelectionTextProperty); - } - set - { - SetValue(SelectionTextProperty, value); - Resize(); - } - } - - public static readonly DependencyProperty SelectionTextProperty = - DependencyProperty.Register("SelectionText", typeof(string), typeof(FilePickerBase), new UIPropertyMetadata("")); - - private void SizeChangedHandler(object sender, SizeChangedEventArgs e) - { - Resize(); - } - - protected override Size MeasureOverride(Size availableSize) - { - var resultSize = new Size(availableSize.Width, 0); - -#if NETCORE - FormattedText formatted = new FormattedText( - "TEST", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), - FontSize, - Foreground, - 100 / 96 - ); -#else - FormattedText formatted = new( - "TEST", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), - FontSize, - Foreground, - VisualTreeHelper.GetDpi(this).PixelsPerDip - ); -#endif - - resultSize.Height = formatted.Height; - - return resultSize; - } - - private void Resize() - { - if (!IsLoaded) - { - return; - } - - CaptionTextBlock.Text = Caption; -#if NETCORE - FormattedText formatted = new FormattedText( - CaptionTextBlock.Text, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(CaptionTextBlock.FontFamily, CaptionTextBlock.FontStyle, CaptionTextBlock.FontWeight, CaptionTextBlock.FontStretch), - FontSize, - Foreground, - 100 / 96 - ); -#else - FormattedText formatted = new( - CaptionTextBlock.Text, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(CaptionTextBlock.FontFamily, CaptionTextBlock.FontStyle, CaptionTextBlock.FontWeight, CaptionTextBlock.FontStretch), - FontSize, - Foreground, - VisualTreeHelper.GetDpi(this).PixelsPerDip - ); -#endif - double CaptionWidth = formatted.Width; - if (CaptionWidth > 0) - { - CaptionWidth += 10; - } - - bool SelectVisible = Path == null; - bool ChangeVisible = Path != null; - bool ClearVisible = (Path != null) && AllowNull; - - double NewWidth = ActualWidth - CaptionWidth; - if (SelectVisible) - { - NewWidth -= SelectLink.ActualWidth; - } - - if (ChangeVisible) - { - NewWidth -= ChangeLink.ActualWidth + 10; - } - - if (ClearVisible) - { - NewWidth -= ClearLink.ActualWidth + 10; - } - - SetText(NewWidth); - - // Calculate the new ActualWidth - // We can't use PathTextBlock.ActualWidth yet, because LayoutUpdated event has not yet been triggered -#if NETCORE - formatted = new FormattedText( - PathTextBlock.Text, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), - FontSize, - Foreground, - 100 / 96 - ); -#else - formatted = new FormattedText( - PathTextBlock.Text, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), - FontSize, - Foreground, - VisualTreeHelper.GetDpi(this).PixelsPerDip - ); -#endif - if (NewWidth < 0) - { - PathTextBlock.Width = 0; - } - else - { - PathTextBlock.Width = formatted.Width > NewWidth ? NewWidth : formatted.Width; - } - - PathTextBlock.Margin = new Thickness(CaptionWidth, 0, 0, 0); - double Pos = PathTextBlock.Width + CaptionWidth; - - if (SelectVisible) - { - SelectLink.Visibility = Visibility.Visible; - SelectLink.Margin = new Thickness(Pos, 0, 0, 0); - Pos += SelectLink.ActualWidth; - } - else - { - SelectLink.Visibility = Visibility.Collapsed; - } - - if (ChangeVisible) - { - ChangeLink.Visibility = Visibility.Visible; - ChangeLink.Margin = new Thickness(Pos + 10, 0, 0, 0); - Pos += ChangeLink.ActualWidth + 10; - } - else - { - ChangeLink.Visibility = Visibility.Collapsed; - } - - if (ClearVisible) - { - ClearLink.Visibility = Visibility.Visible; - ClearLink.Margin = new Thickness(Pos + 10, 0, 0, 0); - Pos += ClearLink.ActualWidth + 10; - } - else - { - ClearLink.Visibility = Visibility.Collapsed; - } - } - - private void SetText(double MaxWidth) - { - string Text = Path; - - if (System.IO.Path.IsPathRooted(Text)) - { - // It is a valid path - string filename = ""; - try - { - filename = System.IO.Path.GetFileName(Text); - } - catch { } - string directory = ""; - try - { - directory = System.IO.Path.GetDirectoryName(Text); - } - catch { } - FormattedText formatted; - bool widthOK = false; - bool changedWidth = false; - - do - { -#if NETCORE - formatted = new FormattedText( - "{0}...\\{1}".FormatWith(directory, filename), - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), - FontSize, - Foreground, - 100 / 96 - ); -#else - formatted = new FormattedText( - "{0}...\\{1}".FormatWith(directory, filename), - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), - FontSize, - Foreground, - VisualTreeHelper.GetDpi(this).PixelsPerDip - ); -#endif - - widthOK = formatted.Width < MaxWidth; - - if (!widthOK) - { - changedWidth = true; - - if (directory.Length > 0) - { - directory = directory[0..^1]; - } - - if (directory.Length == 0) - { - Text = "...\\" + filename; - break; - } - } - - } while (!widthOK); - - if (changedWidth && (directory.Length > 0)) - { - Text = "{0}...\\{1}".FormatWith(directory, filename); - } - } - - PathTextBlock.Text = Text; - } - - private void LoadedHandler(object sender, RoutedEventArgs e) - { - Resize(); - } - - private void SelectLink_Click(object sender, RoutedEventArgs e) - { - Select(); - Resize(); - } - - private void ChangeLink_Click(object sender, RoutedEventArgs e) - { - Select(); - Resize(); - } - - private void ClearLink_Click(object sender, RoutedEventArgs e) - { - Path = null; - Resize(); - } - - protected virtual void Select() { } - } - - public class FilePicker : FilePickerBase - { - public FilePicker() - : base() - { - if (SelectionText?.Length == 0) - { - SelectionText = "Select file..."; - } - } - - protected override void Select() - { - bool? result; - - if (SaveDialog) - { - Microsoft.Win32.SaveFileDialog savedlg = new(); - - savedlg.FileName = Path ?? DefaultFileName; - - // Show open file dialog box - result = savedlg.ShowDialog(); - - // Process open file dialog box results - if (result == true) - { - // Open document - Path = savedlg.FileName; - } - } - else - { - // Select file - Microsoft.Win32.OpenFileDialog dlg = new(); - - dlg.FileName = Path ?? DefaultFileName; - - // Show open file dialog box - result = dlg.ShowDialog(); - - // Process open file dialog box results - if (result == true) - { - // Open document - Path = dlg.FileName; - } - } - } - - public bool SaveDialog - { - get - { - return (bool)GetValue(SaveDialogProperty); - } - set - { - SetValue(SaveDialogProperty, value); - } - } - - public static readonly DependencyProperty SaveDialogProperty = - DependencyProperty.Register("SaveDialog", typeof(bool), typeof(FilePicker), new UIPropertyMetadata(false)); - - public string DefaultFileName - { - get - { - return (string)GetValue(DefaultFileNameProperty); - } - set - { - SetValue(DefaultFileNameProperty, value); - } - } - - public static readonly DependencyProperty DefaultFileNameProperty = - DependencyProperty.Register("DefaultFileName", typeof(string), typeof(FilePicker), new UIPropertyMetadata("")); - } - - public class FolderPicker : FilePickerBase - { - public FolderPicker() - : base() - { - if (SelectionText?.Length == 0) - { - SelectionText = "Select folder..."; - } - } - - protected override void Select() - { - // Select folder - - FolderSelectDialog dlg = new(); - if (Path != null) - { - dlg.InitialDirectory = Path; - } - - if (dlg.ShowDialog()) - { - Path = dlg.FileName; - } - } - } - - internal static class Extensions - { - public static string FormatWith(this string s, params object[] args) - { - return string.Format(s, args); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.ComponentModel; +using System.Globalization; +using System.Threading; +using System.Windows; +using System.Windows.Media; + +namespace WPinternals +{ + public class PathChangedEventArgs : EventArgs + { + public PathChangedEventArgs(string NewPath) + : base() + { + this.NewPath = NewPath; + } + + public string NewPath; + } + + public delegate void PathChangedEventHandler( + Object sender, + PathChangedEventArgs e + ); + + /// + /// Interaction logic for FilePickerControl.xaml + /// + public partial class FilePickerBase : System.Windows.Controls.UserControl, INotifyPropertyChanged + { + private readonly SynchronizationContext UIContext; + + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + public event EventHandler PathChanged = delegate { }; + + protected void OnPropertyChanged(string propertyName) + { + if (this.PropertyChanged != null) + { + if (SynchronizationContext.Current == UIContext) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + else + { + UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); + } + } + } + + public FilePickerBase() + { + UIContext = SynchronizationContext.Current; + InitializeComponent(); + } + + public bool AllowNull + { + get + { + return (bool)GetValue(AllowNullProperty); + } + set + { + SetValue(AllowNullProperty, value); + Resize(); + } + } + + public static readonly DependencyProperty AllowNullProperty = + DependencyProperty.Register("AllowNull", typeof(bool), typeof(FilePickerBase), new UIPropertyMetadata(false)); + + public string Caption + { + get + { + return (string)GetValue(CaptionProperty); + } + set + { + SetValue(CaptionProperty, value); + Resize(); + } + } + + public static readonly DependencyProperty CaptionProperty = + DependencyProperty.Register("Caption", typeof(string), typeof(FilePickerBase), new UIPropertyMetadata(null)); + + public string Path + { + get + { + return (string)GetValue(PathProperty); + } + set + { + if ((string)GetValue(PathProperty) != value) + { + SetValue(PathProperty, value); + Resize(); + PathChanged(this, new PathChangedEventArgs(value)); + } + } + } + + public static readonly DependencyProperty PathProperty = + DependencyProperty.Register("Path", typeof(string), typeof(FilePickerBase), new UIPropertyMetadata(null)); + + public string SelectionText + { + get + { + return (string)GetValue(SelectionTextProperty); + } + set + { + SetValue(SelectionTextProperty, value); + Resize(); + } + } + + public static readonly DependencyProperty SelectionTextProperty = + DependencyProperty.Register("SelectionText", typeof(string), typeof(FilePickerBase), new UIPropertyMetadata("")); + + private void SizeChangedHandler(object sender, SizeChangedEventArgs e) + { + Resize(); + } + + protected override Size MeasureOverride(Size availableSize) + { + var resultSize = new Size(availableSize.Width, 0); + +#if NETCORE + FormattedText formatted = new FormattedText( + "TEST", + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), + FontSize, + Foreground, + 100 / 96 + ); +#else + FormattedText formatted = new( + "TEST", + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), + FontSize, + Foreground, + VisualTreeHelper.GetDpi(this).PixelsPerDip + ); +#endif + + resultSize.Height = formatted.Height; + + return resultSize; + } + + private void Resize() + { + if (!IsLoaded) + { + return; + } + + CaptionTextBlock.Text = Caption; +#if NETCORE + FormattedText formatted = new FormattedText( + CaptionTextBlock.Text, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(CaptionTextBlock.FontFamily, CaptionTextBlock.FontStyle, CaptionTextBlock.FontWeight, CaptionTextBlock.FontStretch), + FontSize, + Foreground, + 100 / 96 + ); +#else + FormattedText formatted = new( + CaptionTextBlock.Text, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(CaptionTextBlock.FontFamily, CaptionTextBlock.FontStyle, CaptionTextBlock.FontWeight, CaptionTextBlock.FontStretch), + FontSize, + Foreground, + VisualTreeHelper.GetDpi(this).PixelsPerDip + ); +#endif + double CaptionWidth = formatted.Width; + if (CaptionWidth > 0) + { + CaptionWidth += 10; + } + + bool SelectVisible = Path == null; + bool ChangeVisible = Path != null; + bool ClearVisible = (Path != null) && AllowNull; + + double NewWidth = ActualWidth - CaptionWidth; + if (SelectVisible) + { + NewWidth -= SelectLink.ActualWidth; + } + + if (ChangeVisible) + { + NewWidth -= ChangeLink.ActualWidth + 10; + } + + if (ClearVisible) + { + NewWidth -= ClearLink.ActualWidth + 10; + } + + SetText(NewWidth); + + // Calculate the new ActualWidth + // We can't use PathTextBlock.ActualWidth yet, because LayoutUpdated event has not yet been triggered +#if NETCORE + formatted = new FormattedText( + PathTextBlock.Text, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), + FontSize, + Foreground, + 100 / 96 + ); +#else + formatted = new FormattedText( + PathTextBlock.Text, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), + FontSize, + Foreground, + VisualTreeHelper.GetDpi(this).PixelsPerDip + ); +#endif + if (NewWidth < 0) + { + PathTextBlock.Width = 0; + } + else + { + PathTextBlock.Width = formatted.Width > NewWidth ? NewWidth : formatted.Width; + } + + PathTextBlock.Margin = new Thickness(CaptionWidth, 0, 0, 0); + double Pos = PathTextBlock.Width + CaptionWidth; + + if (SelectVisible) + { + SelectLink.Visibility = Visibility.Visible; + SelectLink.Margin = new Thickness(Pos, 0, 0, 0); + Pos += SelectLink.ActualWidth; + } + else + { + SelectLink.Visibility = Visibility.Collapsed; + } + + if (ChangeVisible) + { + ChangeLink.Visibility = Visibility.Visible; + ChangeLink.Margin = new Thickness(Pos + 10, 0, 0, 0); + Pos += ChangeLink.ActualWidth + 10; + } + else + { + ChangeLink.Visibility = Visibility.Collapsed; + } + + if (ClearVisible) + { + ClearLink.Visibility = Visibility.Visible; + ClearLink.Margin = new Thickness(Pos + 10, 0, 0, 0); + Pos += ClearLink.ActualWidth + 10; + } + else + { + ClearLink.Visibility = Visibility.Collapsed; + } + } + + private void SetText(double MaxWidth) + { + string Text = Path; + + if (System.IO.Path.IsPathRooted(Text)) + { + // It is a valid path + string filename = ""; + try + { + filename = System.IO.Path.GetFileName(Text); + } + catch { } + string directory = ""; + try + { + directory = System.IO.Path.GetDirectoryName(Text); + } + catch { } + FormattedText formatted; + bool widthOK = false; + bool changedWidth = false; + + do + { +#if NETCORE + formatted = new FormattedText( + "{0}...\\{1}".FormatWith(directory, filename), + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), + FontSize, + Foreground, + 100 / 96 + ); +#else + formatted = new FormattedText( + "{0}...\\{1}".FormatWith(directory, filename), + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(PathTextBlock.FontFamily, PathTextBlock.FontStyle, PathTextBlock.FontWeight, PathTextBlock.FontStretch), + FontSize, + Foreground, + VisualTreeHelper.GetDpi(this).PixelsPerDip + ); +#endif + + widthOK = formatted.Width < MaxWidth; + + if (!widthOK) + { + changedWidth = true; + + if (directory.Length > 0) + { + directory = directory[0..^1]; + } + + if (directory.Length == 0) + { + Text = "...\\" + filename; + break; + } + } + + } while (!widthOK); + + if (changedWidth && (directory.Length > 0)) + { + Text = "{0}...\\{1}".FormatWith(directory, filename); + } + } + + PathTextBlock.Text = Text; + } + + private void LoadedHandler(object sender, RoutedEventArgs e) + { + Resize(); + } + + private void SelectLink_Click(object sender, RoutedEventArgs e) + { + Select(); + Resize(); + } + + private void ChangeLink_Click(object sender, RoutedEventArgs e) + { + Select(); + Resize(); + } + + private void ClearLink_Click(object sender, RoutedEventArgs e) + { + Path = null; + Resize(); + } + + protected virtual void Select() { } + } + + public class FilePicker : FilePickerBase + { + public FilePicker() + : base() + { + if (SelectionText?.Length == 0) + { + SelectionText = "Select file..."; + } + } + + protected override void Select() + { + bool? result; + + if (SaveDialog) + { + Microsoft.Win32.SaveFileDialog savedlg = new(); + + savedlg.FileName = Path ?? DefaultFileName; + + // Show open file dialog box + result = savedlg.ShowDialog(); + + // Process open file dialog box results + if (result == true) + { + // Open document + Path = savedlg.FileName; + } + } + else + { + // Select file + Microsoft.Win32.OpenFileDialog dlg = new(); + + dlg.FileName = Path ?? DefaultFileName; + + // Show open file dialog box + result = dlg.ShowDialog(); + + // Process open file dialog box results + if (result == true) + { + // Open document + Path = dlg.FileName; + } + } + } + + public bool SaveDialog + { + get + { + return (bool)GetValue(SaveDialogProperty); + } + set + { + SetValue(SaveDialogProperty, value); + } + } + + public static readonly DependencyProperty SaveDialogProperty = + DependencyProperty.Register("SaveDialog", typeof(bool), typeof(FilePicker), new UIPropertyMetadata(false)); + + public string DefaultFileName + { + get + { + return (string)GetValue(DefaultFileNameProperty); + } + set + { + SetValue(DefaultFileNameProperty, value); + } + } + + public static readonly DependencyProperty DefaultFileNameProperty = + DependencyProperty.Register("DefaultFileName", typeof(string), typeof(FilePicker), new UIPropertyMetadata("")); + } + + public class FolderPicker : FilePickerBase + { + public FolderPicker() + : base() + { + if (SelectionText?.Length == 0) + { + SelectionText = "Select folder..."; + } + } + + protected override void Select() + { + // Select folder + + FolderSelectDialog dlg = new(); + if (Path != null) + { + dlg.InitialDirectory = Path; + } + + if (dlg.ShowDialog()) + { + Path = dlg.FileName; + } + } + } + + internal static class Extensions + { + public static string FormatWith(this string s, params object[] args) + { + return string.Format(s, args); + } + } +} diff --git a/FolderSelectDialog.cs b/WPinternals/FolderSelectDialog.cs similarity index 96% rename from FolderSelectDialog.cs rename to WPinternals/FolderSelectDialog.cs index 0d0369e..01aeb3e 100644 --- a/FolderSelectDialog.cs +++ b/WPinternals/FolderSelectDialog.cs @@ -1,120 +1,120 @@ -// This class was found online. -// Original author is probably: Swizzy -// https://github.com/ttgxdinger/Random/blob/master/CPUKey%20Checker/CPUKey%20Checker/FolderSelectDialog.cs - -using System; -using System.Windows.Forms; - -namespace WPinternals -{ - /// - /// Wraps System.Windows.Forms.OpenFileDialog to make it present - /// a vista-style dialog. - /// - public class FolderSelectDialog - { - // Wrapped dialog - private readonly OpenFileDialog ofd = null; - - /// - /// Default constructor - /// - public FolderSelectDialog() - { - ofd = new OpenFileDialog - { - Filter = "Folders|\n", - AddExtension = false, - CheckFileExists = false, - DereferenceLinks = true, - Multiselect = false - }; - } - - #region Properties - - /// - /// Gets/Sets the initial folder to be selected. A null value selects the current directory. - /// - public string InitialDirectory - { - get { return ofd.InitialDirectory; } - set { ofd.InitialDirectory = string.IsNullOrEmpty(value) ? Environment.CurrentDirectory : value; } - } - - /// - /// Gets/Sets the title to show in the dialog - /// - public string Title - { - get { return ofd.Title; } - set { ofd.Title = value ?? "Select a folder"; } - } - - /// - /// Gets the selected folder - /// - public string FileName - { - get { return ofd.FileName; } - } - - #endregion - - #region Methods - - /// - /// Shows the dialog - /// - /// True if the user presses OK else false - public bool ShowDialog() - { - return ShowDialog(IntPtr.Zero); - } - - /// - /// Shows the dialog - /// - /// Handle of the control to be parent - /// True if the user presses OK else false - public bool ShowDialog(IntPtr hWndOwner) - { - var fbd = new FolderBrowserDialog - { - Description = this.Title, - SelectedPath = this.InitialDirectory, - ShowNewFolderButton = false - }; - if (fbd.ShowDialog(new WindowWrapper(hWndOwner)) != DialogResult.OK) - { - return false; - } - - ofd.FileName = fbd.SelectedPath; - - return true; - } - - #endregion - } - - /// - /// Creates IWin32Window around an IntPtr - /// - public class WindowWrapper : IWin32Window - { - /// - /// Constructor - /// - /// Handle to wrap - public WindowWrapper(IntPtr handle) - { - Handle = handle; - } - - /// - /// Original ptr - /// - public IntPtr Handle { get; } - } -} +// This class was found online. +// Original author is probably: Swizzy +// https://github.com/ttgxdinger/Random/blob/master/CPUKey%20Checker/CPUKey%20Checker/FolderSelectDialog.cs + +using System; +using System.Windows.Forms; + +namespace WPinternals +{ + /// + /// Wraps System.Windows.Forms.OpenFileDialog to make it present + /// a vista-style dialog. + /// + public class FolderSelectDialog + { + // Wrapped dialog + private readonly OpenFileDialog ofd = null; + + /// + /// Default constructor + /// + public FolderSelectDialog() + { + ofd = new OpenFileDialog + { + Filter = "Folders|\n", + AddExtension = false, + CheckFileExists = false, + DereferenceLinks = true, + Multiselect = false + }; + } + + #region Properties + + /// + /// Gets/Sets the initial folder to be selected. A null value selects the current directory. + /// + public string InitialDirectory + { + get { return ofd.InitialDirectory; } + set { ofd.InitialDirectory = string.IsNullOrEmpty(value) ? Environment.CurrentDirectory : value; } + } + + /// + /// Gets/Sets the title to show in the dialog + /// + public string Title + { + get { return ofd.Title; } + set { ofd.Title = value ?? "Select a folder"; } + } + + /// + /// Gets the selected folder + /// + public string FileName + { + get { return ofd.FileName; } + } + + #endregion + + #region Methods + + /// + /// Shows the dialog + /// + /// True if the user presses OK else false + public bool ShowDialog() + { + return ShowDialog(IntPtr.Zero); + } + + /// + /// Shows the dialog + /// + /// Handle of the control to be parent + /// True if the user presses OK else false + public bool ShowDialog(IntPtr hWndOwner) + { + var fbd = new FolderBrowserDialog + { + Description = this.Title, + SelectedPath = this.InitialDirectory, + ShowNewFolderButton = false + }; + if (fbd.ShowDialog(new WindowWrapper(hWndOwner)) != DialogResult.OK) + { + return false; + } + + ofd.FileName = fbd.SelectedPath; + + return true; + } + + #endregion + } + + /// + /// Creates IWin32Window around an IntPtr + /// + public class WindowWrapper : IWin32Window + { + /// + /// Constructor + /// + /// Handle to wrap + public WindowWrapper(IntPtr handle) + { + Handle = handle; + } + + /// + /// Original ptr + /// + public IntPtr Handle { get; } + } +} diff --git a/HelperClasses.cs b/WPinternals/HelperClasses.cs similarity index 97% rename from HelperClasses.cs rename to WPinternals/HelperClasses.cs index 0b680d3..fcb6cdc 100644 --- a/HelperClasses.cs +++ b/WPinternals/HelperClasses.cs @@ -1,2352 +1,2352 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// -// Some of the classes and functions in this file were found online. -// Where possible the original authors are referenced. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media.Animation; -using System.Windows.Media.Imaging; -using System.Windows.Threading; - -namespace WPinternals -{ - internal delegate void SetWorkingStatus(string Message, string SubMessage = null, ulong? MaxProgressValue = null, bool ShowAnimation = true, WPinternalsStatus Status = WPinternalsStatus.Undefined); - internal delegate void UpdateWorkingStatus(string Message, string SubMessage = null, ulong? CurrentProgressValue = null, WPinternalsStatus Status = WPinternalsStatus.Undefined); - internal delegate void ExitSuccess(string Message, string SubMessage = null); - internal delegate void ExitFailure(string Message, string SubMessage = null); - - internal enum WPinternalsStatus - { - Undefined, - Scanning, - Flashing, - Patching, - WaitingForManualReset, - SwitchingMode, - Initializing - }; - - public class BooleanConverter : DependencyObject, IValueConverter - { - public static readonly DependencyProperty OnTrueProperty = - DependencyProperty.Register("OnTrue", typeof(object), typeof(BooleanConverter), - new PropertyMetadata(default(object))); - - public static readonly DependencyProperty OnFalseProperty = - DependencyProperty.Register("OnFalse", typeof(object), typeof(BooleanConverter), - new PropertyMetadata(default(object))); - - public static readonly DependencyProperty OnNullProperty = - DependencyProperty.Register("OnNull", typeof(object), typeof(BooleanConverter), - new PropertyMetadata(default(object))); - - public object OnTrue - { - get { return GetValue(OnTrueProperty); } - set { SetValue(OnTrueProperty, value); } - } - - public object OnFalse - { - get { return GetValue(OnFalseProperty); } - set { SetValue(OnFalseProperty, value); } - } - - public object OnNull - { - get { return GetValue(OnNullProperty); } - set { SetValue(OnNullProperty, value); } - } - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value == null - ? OnNull ?? Default(targetType) - : (string.Equals(value.ToString(), false.ToString(), StringComparison.CurrentCultureIgnoreCase) - ? OnFalse - : OnTrue); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value == OnNull) - { - return Default(targetType); - } - - if (value == OnFalse) - { - return false; - } - - if (value == OnTrue) - { - return true; - } - - if (value == null) - { - return null; - } - - if (OnNull != null && - string.Equals(value.ToString(), OnNull.ToString(), StringComparison.CurrentCultureIgnoreCase)) - { - return Default(targetType); - } - - if (OnFalse != null && - string.Equals(value.ToString(), OnFalse.ToString(), StringComparison.CurrentCultureIgnoreCase)) - { - return false; - } - - if (OnTrue != null && - string.Equals(value.ToString(), OnTrue.ToString(), StringComparison.CurrentCultureIgnoreCase)) - { - return true; - } - - return null; - } - - public static object Default(Type type) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } - } - - public class HexConverter : DependencyObject, IValueConverter - { - public static readonly DependencyProperty SeparatorProperty = - DependencyProperty.Register("OnTrue", typeof(object), typeof(HexConverter), - new PropertyMetadata(" ")); - - public object Separator - { - get { return GetValue(SeparatorProperty); } - set { SetValue(SeparatorProperty, value); } - } - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is byte[] bytes) - { - StringBuilder s = new(1000); - for (int i = bytes.GetLowerBound(0); i <= bytes.GetUpperBound(0); i++) - { - if (i != bytes.GetLowerBound(0)) - { - s.Append(Separator); - } - - s.Append(bytes[i].ToString("X2")); - } - return s.ToString(); - } - else - { - return ""; - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - - public static object Default(Type type) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } - } - - public class ObjectToVisibilityConverter : DependencyObject, IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value == null) - { - return "Collapsed"; - } - else - { - return "Visible"; - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - - public static object Default(Type type) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } - } - - public class InverseObjectToVisibilityConverter : DependencyObject, IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value == null) - { - return "Visible"; - } - else - { - return "Collapsed"; - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - - public static object Default(Type type) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } - } - - internal class CollapsibleRun : Run - { - private string CollapsibleText; - - protected override void OnInitialized(EventArgs e) - { - base.OnInitialized(e); - - CollapsibleText = Text; - } - - public Boolean IsVisible - { - get - { - return (Boolean)this.GetValue(IsVisibleProperty); - } - set - { - this.SetValue(IsVisibleProperty, value); - } - } - public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.Register( - "IsVisible", typeof(Boolean), typeof(CollapsibleRun), new PropertyMetadata(true, IsVisibleChanged)); - public static void IsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((CollapsibleRun)d).Text = (bool)e.NewValue ? ((CollapsibleRun)d).CollapsibleText : String.Empty; - } - } - - internal class CollapsibleSection : Section - { - public CollapsibleSection() - { - CollapsibleBlocks = new List(); - } - - protected override void OnInitialized(EventArgs e) - { - base.OnInitialized(e); - - foreach (Block Block in Blocks) - { - CollapsibleBlocks.Add(Block); - } - - Blocks.Clear(); - } - - public List CollapsibleBlocks { get; } - - public Boolean IsCollapsed - { - get - { - return (Boolean)this.GetValue(IsCollapsedProperty); - } - set - { - this.SetValue(IsCollapsedProperty, value); - - Blocks.Clear(); - - if (IsInitialized && !value) - { - foreach (Block Block in CollapsibleBlocks) - { - Blocks.Add(Block); - } - } - } - } - public static readonly DependencyProperty IsCollapsedProperty = DependencyProperty.Register( - "IsCollapsed", typeof(Boolean), typeof(CollapsibleSection), new PropertyMetadata(false)); - } - - // Overloaded Paragraph class to remove empty Run-elements, caused by auto-formatting new-lines in the XAML - // Use local:Paragraph in a FlowDocument - // This correction only works at run-time - public class Paragraph : System.Windows.Documents.Paragraph - { - protected override void OnInitialized(EventArgs e) - { - base.OnInitialized(e); - int inlinesCount = this.Inlines.Count; - for (int i = 0; i < inlinesCount; i++) - { - Inline inline = this.Inlines.ElementAt(i); - if (inline is Run run) - { - if (run.Text == Convert.ToChar(32).ToString()) //ACSII 32 is the white space - { - run.Text = string.Empty; - } - } - } - } - } - - internal class ArrivalEventArgs : EventArgs - { - public PhoneInterfaces NewInterface; - public IDisposable NewModel; - - public ArrivalEventArgs(PhoneInterfaces NewInterface, IDisposable NewModel) - : base() - { - this.NewInterface = NewInterface; - this.NewModel = NewModel; - } - } - - internal static class BigEndian - { - public static byte[] GetBytes(object Value) - { - byte[] Bytes; - if (Value is short) - { - Bytes = BitConverter.GetBytes((short)Value); - } - else if (Value is ushort) - { - Bytes = BitConverter.GetBytes((ushort)Value); - } - else if (Value is int) - { - Bytes = BitConverter.GetBytes((int)Value); - } - else - { - Bytes = Value is uint ? BitConverter.GetBytes((uint)Value) : throw new NotSupportedException(); - } - - byte[] Result = new byte[Bytes.Length]; - for (int i = 0; i < Bytes.Length; i++) - { - Result[i] = Bytes[Bytes.Length - 1 - i]; - } - - return Result; - } - - public static byte[] GetBytes(object Value, int Width) - { - byte[] Result; - byte[] BigEndianBytes = GetBytes(Value); - if (BigEndianBytes.Length == Width) - { - return BigEndianBytes; - } - else if (BigEndianBytes.Length > Width) - { - Result = new byte[Width]; - Buffer.BlockCopy(BigEndianBytes, BigEndianBytes.Length - Width, Result, 0, Width); - return Result; - } - else - { - Result = new byte[Width]; - Buffer.BlockCopy(BigEndianBytes, 0, Result, Width - BigEndianBytes.Length, BigEndianBytes.Length); - return Result; - } - } - - public static UInt16 ToUInt16(byte[] Buffer, int Offset) - { - byte[] Bytes = new byte[2]; - for (int i = 0; i < 2; i++) - { - Bytes[i] = Buffer[Offset + 1 - i]; - } - - return BitConverter.ToUInt16(Bytes, 0); - } - - public static Int16 ToInt16(byte[] Buffer, int Offset) - { - byte[] Bytes = new byte[2]; - for (int i = 0; i < 2; i++) - { - Bytes[i] = Buffer[Offset + 1 - i]; - } - - return BitConverter.ToInt16(Bytes, 0); - } - - public static UInt32 ToUInt32(byte[] Buffer, int Offset) - { - byte[] Bytes = new byte[4]; - for (int i = 0; i < 4; i++) - { - Bytes[i] = Buffer[Offset + 3 - i]; - } - - return BitConverter.ToUInt32(Bytes, 0); - } - - public static Int32 ToInt32(byte[] Buffer, int Offset) - { - byte[] Bytes = new byte[4]; - for (int i = 0; i < 4; i++) - { - Bytes[i] = Buffer[Offset + 3 - i]; - } - - return BitConverter.ToInt32(Bytes, 0); - } - } - - // This class was found online. - // Original author is probably: mdm20 - // https://stackoverflow.com/questions/5566330/get-gif-to-play-in-wpf-with-gifimage-class/5568703#5568703 - internal class GifImage : Image - { - private bool _isInitialized; - private GifBitmapDecoder _gifDecoder; - private Int32Animation _animation; - - public int FrameIndex - { - get { return (int)GetValue(FrameIndexProperty); } - set { SetValue(FrameIndexProperty, value); } - } - - private void Initialize() - { - _gifDecoder = new GifBitmapDecoder(new Uri("pack://application:,,," + this.GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); - _animation = new Int32Animation(0, _gifDecoder.Frames.Count - 1, new Duration(new TimeSpan(0, 0, 0, _gifDecoder.Frames.Count / 10, (int)(((_gifDecoder.Frames.Count / 10.0) - (_gifDecoder.Frames.Count / 10)) * 1000)))) - { - RepeatBehavior = RepeatBehavior.Forever - }; - this.Source = _gifDecoder.Frames[0]; - - _isInitialized = true; - } - - static GifImage() - { - VisibilityProperty.OverrideMetadata(typeof(GifImage), - new FrameworkPropertyMetadata(VisibilityPropertyChanged)); - } - - private static void VisibilityPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) - { - if ((Visibility)e.NewValue == Visibility.Visible) - { - ((GifImage)sender).StartAnimation(); - } - else - { - ((GifImage)sender).StopAnimation(); - } - } - - public static readonly DependencyProperty FrameIndexProperty = - DependencyProperty.Register("FrameIndex", typeof(int), typeof(GifImage), new UIPropertyMetadata(0, new PropertyChangedCallback(ChangingFrameIndex))); - - private static void ChangingFrameIndex(DependencyObject obj, DependencyPropertyChangedEventArgs ev) - { - var gifImage = obj as GifImage; - gifImage.Source = gifImage._gifDecoder.Frames[(int)ev.NewValue]; - } - - public bool AutoStart - { - get { return (bool)GetValue(AutoStartProperty); } - set { SetValue(AutoStartProperty, value); } - } - - public static readonly DependencyProperty AutoStartProperty = - DependencyProperty.Register("AutoStart", typeof(bool), typeof(GifImage), new UIPropertyMetadata(false, AutoStartPropertyChanged)); - - private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) - { - if ((bool)e.NewValue) - { - (sender as GifImage)?.StartAnimation(); - } - } - - public string GifSource - { - get { return (string)GetValue(GifSourceProperty); } - set { SetValue(GifSourceProperty, value); } - } - - public static readonly DependencyProperty GifSourceProperty = - DependencyProperty.Register("GifSource", typeof(string), typeof(GifImage), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged)); - - private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) - { - (sender as GifImage)?.Initialize(); - } - - public void StartAnimation() - { - if (!_isInitialized) - { - this.Initialize(); - } - - BeginAnimation(FrameIndexProperty, _animation); - } - - public void StopAnimation() - { - BeginAnimation(FrameIndexProperty, null); - } - } - - internal enum LogType - { - FileOnly, - FileAndConsole, - ConsoleOnly - }; - - internal static class LogFile - { - private static readonly StreamWriter w = null; - private static readonly object lockobject = new(); -#if PREVIEW - private static string LogAction = null; - private static StringBuilder LogBuilder; -#endif - - static LogFile() - { - try - { - if (!Directory.Exists(Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals"))) - { - Directory.CreateDirectory(Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals")); - } - - w = File.AppendText(Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\WPInternals.log")); - } - catch { } - } - - public static void Log(string logMessage, LogType Type = LogType.FileOnly) - { - if (w == null) - { - return; - } - - lock (lockobject) - { - if ((Type == LogType.FileOnly) || (Type == LogType.FileAndConsole)) - { - DateTime Now = DateTime.Now; - string Text = Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + ": " + logMessage; - w.WriteLine(Text); - w.Flush(); - -#if PREVIEW - if (LogAction != null) - LogBuilder.AppendLine(Text); -#endif - } - - if (CommandLine.IsConsoleVisible && ((Type == LogType.ConsoleOnly) || (Type == LogType.FileAndConsole))) - { - Console.WriteLine(logMessage); - } - } - } - - public static void LogException(Exception Ex, LogType Type = LogType.FileAndConsole, string AdditionalInfo = null) - { - string Indent = ""; - Exception CurrentEx = Ex; - while (CurrentEx != null) - { - Log(Indent + "Error: " + RemoveBadChars(CurrentEx.Message).Replace("of type '.' ", "") + (AdditionalInfo == null ? "" : " - " + AdditionalInfo), Type); - AdditionalInfo = null; - if (CurrentEx is WPinternalsException) - { - Log(Indent + ((WPinternalsException)CurrentEx).SubMessage, Type); - } -#if DEBUG - if (CurrentEx.StackTrace != null) - { - Log(Indent + CurrentEx.StackTrace, LogType.FileOnly); - } -#endif - Indent += " "; - CurrentEx = CurrentEx.InnerException; - } - } - - private static string RemoveBadChars(string Text) - { - return System.Text.RegularExpressions.Regex.Replace(Text, @"[^\u0020-\u007E]+", string.Empty); - } - - public static void DumpLog(StreamReader r) - { - string line; - while ((line = r.ReadLine()) != null) - { - Console.WriteLine(line); - } - } - - public static void LogApplicationVersion() - { - Log("Windows Phone Internals version " + - Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Build.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString(), LogType.FileAndConsole); - Log("Copyright Heathcliff74", LogType.FileAndConsole); - } - - internal static void BeginAction(string Action) - { -#if PREVIEW - if (LogAction == null) - { - LogAction = Action; - LogBuilder = new StringBuilder(); - LogBuilder.AppendLine("Windows Phone Internals version " + - Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Build.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString()); - LogBuilder.AppendLine("Copyright Heathcliff74"); - LogBuilder.AppendLine("Action: " + Action); - if (App.Config.RegistrationName != null) - LogBuilder.AppendLine("Name: " + App.Config.RegistrationName); - if (App.Config.RegistrationEmail != null) - LogBuilder.AppendLine("Mail: " + App.Config.RegistrationEmail); - if (App.Config.RegistrationSkypeID != null) - LogBuilder.AppendLine("Skype: " + App.Config.RegistrationSkypeID); - if (App.Config.RegistrationTelegramID != null) - LogBuilder.AppendLine("Telegram: " + App.Config.RegistrationTelegramID); - if (Environment.MachineName != null) - LogBuilder.AppendLine("Machine: " + Environment.MachineName); - } -#endif - } - - internal static void EndAction() - { - EndAction(null); - } - - internal static void EndAction(string Action) - { -#if PREVIEW - if ((LogAction != null) && ((Action == null) || (LogAction == Action))) - { - Action = LogAction; - LogAction = null; - string FileName = ""; - if (App.Config.RegistrationName != null) - FileName += App.Config.RegistrationName + " - "; - FileName += DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + " - " + Action + " - " + - Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Build.ToString() + "." + - Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString() + ".log"; - - // Normalize filename - try - { - FileName = System.Text.Encoding.ASCII.GetString(System.Text.Encoding.GetEncoding("ISO-8859-8").GetBytes(FileName)); - } - catch { } - FileName = FileName.Replace("?", ""); - - if (Action.ToLower() == "registration") - Uploader.Upload(FileName, LogBuilder.ToString()); - else - { - try - { - Uploader.Upload(FileName, LogBuilder.ToString()); - } - catch { } - } - } -#endif - } - } - - public static class Converter - { - public static string ConvertHexToString(byte[] Bytes, string Separator) - { - StringBuilder s = new(1000); - for (int i = Bytes.GetLowerBound(0); i <= Bytes.GetUpperBound(0); i++) - { - if (i != Bytes.GetLowerBound(0)) - { - s.Append(Separator); - } - - s.Append(Bytes[i].ToString("X2")); - } - return s.ToString(); - } - - public static byte[] ConvertStringToHex(string HexString) - { - if (HexString.Length % 2 == 1) - { - throw new Exception("The binary key cannot have an odd number of digits"); - } - - byte[] arr = new byte[HexString.Length >> 1]; - - for (int i = 0; i < (HexString.Length >> 1); ++i) - { - arr[i] = (byte)((GetHexVal(HexString[i << 1]) << 4) + GetHexVal(HexString[(i << 1) + 1])); - } - - return arr; - } - - public static int GetHexVal(char hex) - { - int val = hex; - //For uppercase A-F letters: - //return val - (val < 58 ? 48 : 55); - //For lowercase a-f letters: - //return val - (val < 58 ? 48 : 87); - //Or the two combined, but a bit slower: - return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); - } - } - - // This class was found online. - // Original author is probably: John Melville - // https://social.msdn.microsoft.com/Forums/en-US/163ef755-ff7b-4ea5-b226-bbe8ef5f4796/is-there-a-pattern-for-calling-an-async-method-synchronously?forum=async - public static class AsyncHelpers - { - /// - /// Execute's an async Task method which has a void return value synchronously - /// - /// Task method to execute - public static void RunSync(Func task) - { - var oldContext = SynchronizationContext.Current; - var synch = new ExclusiveSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(synch); - synch.Post(async _ => - { - try - { - await task(); - } - catch (Exception e) - { - synch.InnerException = e; - throw; - } - finally - { - synch.EndMessageLoop(); - } - }, null); - synch.BeginMessageLoop(); - - SynchronizationContext.SetSynchronizationContext(oldContext); - } - - /// - /// Execute's an async Task method which has a T return type synchronously - /// - /// Return Type - /// Task method to execute - /// - public static T RunSync(Func> task) - { - var oldContext = SynchronizationContext.Current; - var synch = new ExclusiveSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(synch); - T ret = default; - synch.Post(async _ => - { - try - { - ret = await task(); - } - catch (Exception e) - { - synch.InnerException = e; - throw; - } - finally - { - synch.EndMessageLoop(); - } - }, null); - synch.BeginMessageLoop(); - SynchronizationContext.SetSynchronizationContext(oldContext); - return ret; - } - - private class ExclusiveSynchronizationContext : SynchronizationContext - { - private bool done; - public Exception InnerException { get; set; } - private readonly AutoResetEvent workItemsWaiting = new(false); - private readonly Queue> items = - new(); - - public override void Send(SendOrPostCallback d, object state) - { - throw new NotSupportedException("We cannot send to our same thread"); - } - - public override void Post(SendOrPostCallback d, object state) - { - lock (items) - { - items.Enqueue(Tuple.Create(d, state)); - } - workItemsWaiting.Set(); - } - - public void EndMessageLoop() - { - Post(_ => done = true, null); - } - - public void BeginMessageLoop() - { - while (!done) - { - Tuple task = null; - lock (items) - { - if (items.Count > 0) - { - task = items.Dequeue(); - } - } - if (task != null) - { - task.Item1(task.Item2); - if (InnerException != null) // the method threw an exeption - { - throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException); - } - } - else - { - workItemsWaiting.WaitOne(); - } - } - } - - public override SynchronizationContext CreateCopy() - { - return this; - } - } - } - - // This class is taken from the Prism library by Microsoft Patterns & Practices - // License: http://compositewpf.codeplex.com/license - internal static class WeakEventHandlerManager - { - public static void AddWeakReferenceHandler(ref List handlers, EventHandler handler, int defaultListSize) - { - (handlers ??= (defaultListSize > 0) ? new List(defaultListSize) : new List()).Add(new WeakReference(handler)); - } - - private static void CallHandler(object sender, EventHandler eventHandler) - { - DispatcherProxy proxy = DispatcherProxy.CreateDispatcher(); - if (eventHandler != null) - { - if (proxy?.CheckAccess() == false) - { - proxy.BeginInvoke(new Action(CallHandler), new object[] { sender, eventHandler }); - } - else - { - eventHandler(sender, EventArgs.Empty); - } - } - } - - public static void CallWeakReferenceHandlers(object sender, List handlers) - { - if (handlers != null) - { - EventHandler[] callees = new EventHandler[handlers.Count]; - int count = 0; - count = CleanupOldHandlers(handlers, callees, count); - for (int i = 0; i < count; i++) - { - CallHandler(sender, callees[i]); - } - } - } - - private static int CleanupOldHandlers(List handlers, EventHandler[] callees, int count) - { - for (int i = handlers.Count - 1; i >= 0; i--) - { - WeakReference reference = handlers[i]; - if (reference.Target is not EventHandler target) - { - handlers.RemoveAt(i); - } - else - { - callees[count] = target; - count++; - } - } - return count; - } - - public static void RemoveWeakReferenceHandler(List handlers, EventHandler handler) - { - if (handlers != null) - { - for (int i = handlers.Count - 1; i >= 0; i--) - { - WeakReference reference = handlers[i]; - if ((reference.Target is not EventHandler target) || (target == handler)) - { - handlers.RemoveAt(i); - } - } - } - } - - private class DispatcherProxy - { - private readonly Dispatcher innerDispatcher; - - private DispatcherProxy(Dispatcher dispatcher) - { - this.innerDispatcher = dispatcher; - } - - public DispatcherOperation BeginInvoke(Delegate method, params object[] args) - { - return this.innerDispatcher.BeginInvoke(method, DispatcherPriority.Normal, args); - } - - public bool CheckAccess() - { - return this.innerDispatcher.CheckAccess(); - } - - public static DispatcherProxy CreateDispatcher() - { - if (Application.Current == null) - { - return null; - } - return new DispatcherProxy(Application.Current.Dispatcher); - } - } - } - - // This interface is taken from the Prism library by Microsoft Patterns & Practices - // License: http://compositewpf.codeplex.com/license - public interface IActiveAware - { - /// - /// Gets or sets a value indicating whether the object is active. - /// - /// if the object is active; otherwise . - bool IsActive { get; set; } - - /// - /// Notifies that the value for property has changed. - /// - event EventHandler IsActiveChanged; - } - - // This class is taken from the Prism library by Microsoft Patterns & Practices - // License: http://compositewpf.codeplex.com/license - public abstract class DelegateCommandBase : ICommand, IActiveAware - { - private List _canExecuteChangedHandlers; - private bool _isActive; - private readonly Func canExecuteMethod; - private readonly Action executeMethod; - - public event EventHandler CanExecuteChanged - { - add - { - WeakEventHandlerManager.AddWeakReferenceHandler(ref this._canExecuteChangedHandlers, value, 2); - } - remove - { - WeakEventHandlerManager.RemoveWeakReferenceHandler(this._canExecuteChangedHandlers, value); - } - } - - public event EventHandler IsActiveChanged; - - protected DelegateCommandBase(Action executeMethod, Func canExecuteMethod) - { - if ((executeMethod == null) || (canExecuteMethod == null)) - { - throw new ArgumentNullException(nameof(executeMethod), "Delegate Command Delegates Cannot Be Null"); - } - this.executeMethod = executeMethod; - this.canExecuteMethod = canExecuteMethod; - } - - protected bool CanExecute(object parameter) - { - if (this.canExecuteMethod != null) - { - return this.canExecuteMethod(parameter); - } - return true; - } - - protected void Execute(object parameter) - { - this.executeMethod(parameter); - } - - protected virtual void OnCanExecuteChanged() - { - WeakEventHandlerManager.CallWeakReferenceHandlers(this, this._canExecuteChangedHandlers); - } - - protected virtual void OnIsActiveChanged() - { - this.IsActiveChanged?.Invoke(this, EventArgs.Empty); - } - - public void RaiseCanExecuteChanged() - { - this.OnCanExecuteChanged(); - } - - bool ICommand.CanExecute(object parameter) - { - return this.CanExecute(parameter); - } - - void ICommand.Execute(object parameter) - { - this.Execute(parameter); - } - - public bool IsActive - { - get - { - return this._isActive; - } - set - { - if (this._isActive != value) - { - this._isActive = value; - this.OnIsActiveChanged(); - } - } - } - } - - // This class is taken from the Prism library by Microsoft Patterns & Practices - // License: http://compositewpf.codeplex.com/license - public class DelegateCommand : DelegateCommandBase - { - public DelegateCommand(Action executeMethod) - : this(executeMethod, () => true) - { - } - - public DelegateCommand(Action executeMethod, Func canExecuteMethod) : base(o => executeMethod(), f => canExecuteMethod()) - { - } - - public bool CanExecute() - { - return CanExecute(null); - } - - public void Execute() - { - Execute(null); - } - } - - internal class FlowDocumentScrollViewerNoMouseWheel : FlowDocumentScrollViewer - { - protected override void OnMouseWheel(MouseWheelEventArgs e) - { - } - } - - internal class ProgressUpdater - { - private readonly DateTime InitTime; - private DateTime LastUpdateTime; - private readonly UInt64 MaxValue; - private readonly Action ProgressUpdateCallback; - internal int ProgressPercentage; - - internal ProgressUpdater(UInt64 MaxValue, Action ProgressUpdateCallback) - { - InitTime = DateTime.Now; - LastUpdateTime = DateTime.Now; - this.MaxValue = MaxValue; - this.ProgressUpdateCallback = ProgressUpdateCallback; - SetProgress(0); - } - - private UInt64 _Progress; - internal UInt64 Progress - { - get - { - return _Progress; - } - } - - internal void SetProgress(UInt64 NewValue) - { - if (_Progress != NewValue) - { - int PreviousProgressPercentage = (int)((double)_Progress / MaxValue * 100); - ProgressPercentage = (int)((double)NewValue / MaxValue * 100); - - _Progress = NewValue; - - if (((DateTime.Now - LastUpdateTime) > TimeSpan.FromSeconds(0.5)) || (ProgressPercentage == 100)) - { -#if DEBUG - Console.WriteLine("Init time: " + InitTime.ToShortTimeString() + " / Now: " + DateTime.Now.ToString() + " / NewValue: " + NewValue.ToString() + " / MaxValue: " + MaxValue.ToString() + " ->> Percentage: " + ProgressPercentage.ToString() + " / Remaining: " + TimeSpan.FromTicks((long)((DateTime.Now - InitTime).Ticks / ((double)NewValue / MaxValue) * (1 - ((double)NewValue / MaxValue)))).ToString()); -#endif - - if (((DateTime.Now - InitTime) < TimeSpan.FromSeconds(30)) && (ProgressPercentage < 15)) - { - ProgressUpdateCallback(ProgressPercentage, null); - } - else - { - ProgressUpdateCallback(ProgressPercentage, TimeSpan.FromTicks((long)((DateTime.Now - InitTime).Ticks / ((double)NewValue / MaxValue) * (1 - ((double)NewValue / MaxValue))))); - } - - LastUpdateTime = DateTime.Now; - } - } - } - - internal void IncreaseProgress(UInt64 Progress) - { - SetProgress(_Progress + Progress); - } - } - - internal static class Compression - { - internal static Stream GetDecompressedStreamWithSeek(Stream InputStream) - { - long P = InputStream.Position; - byte[] GZipHeader = new byte[3]; - InputStream.Read(GZipHeader, 0, 3); - InputStream.Position = P; - if (StructuralComparisons.StructuralEqualityComparer.Equals(GZipHeader, new byte[] { 0x1F, 0x8B, 0x08 })) - { - return new GZipStream(InputStream, CompressionMode.Decompress, false); - } - else - { - return InputStream; - } - } - - internal static bool IsCompressedStream(Stream InputStream) - { - byte[] GZipHeader = new byte[3]; - InputStream.Read(GZipHeader, 0, 3); - return StructuralComparisons.StructuralEqualityComparer.Equals(GZipHeader, new byte[] { 0x1F, 0x8B, 0x08 }); - } - - internal static GZipStream GetDecompressedStream(Stream InputStream) - { - return new GZipStream(InputStream, CompressionMode.Decompress, false); - } - } - - internal class WPinternalsException : Exception - { - // Message and SubMessaage are always printable - internal string SubMessage = null; - - internal WPinternalsException() : base() { } - - internal WPinternalsException(string Message) : base(Message) { } - - internal WPinternalsException(string Message, Exception InnerException) : base(Message, InnerException) { } - - internal WPinternalsException(string Message, string SubMessage) : base(Message) { this.SubMessage = SubMessage; } - - internal WPinternalsException(string Message, string SubMessage, Exception InnerException) : base(Message, InnerException) { this.SubMessage = SubMessage; } - } - - // This class is written by: Eugene Beresovsky - // https://stackoverflow.com/questions/13035925/stream-wrapper-to-make-stream-seekable/28036366#28036366 - internal class ReadSeekableStream : Stream - { - private long _underlyingPosition; - private readonly byte[] _seekBackBuffer; - private int _seekBackBufferCount; - private int _seekBackBufferIndex; - private readonly Stream _underlyingStream; - - public ReadSeekableStream(Stream underlyingStream, int seekBackBufferSize) - { - if (!underlyingStream.CanRead) - { - throw new Exception("Provided stream " + underlyingStream + " is not readable"); - } - - _underlyingStream = underlyingStream; - _seekBackBuffer = new byte[seekBackBufferSize]; - } - - public override bool CanRead { get { return true; } } - public override bool CanSeek { get { return true; } } - - public override int Read(byte[] buffer, int offset, int count) - { - int copiedFromBackBufferCount = 0; - if (_seekBackBufferIndex < _seekBackBufferCount) - { - copiedFromBackBufferCount = Math.Min(count, _seekBackBufferCount - _seekBackBufferIndex); - Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferIndex, buffer, offset, copiedFromBackBufferCount); - offset += copiedFromBackBufferCount; - count -= copiedFromBackBufferCount; - _seekBackBufferIndex += copiedFromBackBufferCount; - } - int bytesReadFromUnderlying = 0; - if (count > 0) - { - bytesReadFromUnderlying = _underlyingStream.Read(buffer, offset, count); - if (bytesReadFromUnderlying > 0) - { - _underlyingPosition += bytesReadFromUnderlying; - - var copyToBufferCount = Math.Min(bytesReadFromUnderlying, _seekBackBuffer.Length); - var copyToBufferOffset = Math.Min(_seekBackBufferCount, _seekBackBuffer.Length - copyToBufferCount); - var bufferBytesToMove = Math.Min(_seekBackBufferCount - 1, copyToBufferOffset); - - if (bufferBytesToMove > 0) - { - Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferCount - bufferBytesToMove, _seekBackBuffer, 0, bufferBytesToMove); - } - - Buffer.BlockCopy(buffer, offset, _seekBackBuffer, copyToBufferOffset, copyToBufferCount); - _seekBackBufferCount = Math.Min(_seekBackBuffer.Length, _seekBackBufferCount + copyToBufferCount); - _seekBackBufferIndex = _seekBackBufferCount; - } - } - return copiedFromBackBufferCount + bytesReadFromUnderlying; - } - - public override long Seek(long offset, SeekOrigin origin) - { - if (origin == SeekOrigin.End) - { - return SeekFromEnd((int)Math.Max(0, -offset)); - } - - var relativeOffset = origin == SeekOrigin.Current - ? offset - : offset - Position; - - if (relativeOffset == 0) - { - return Position; - } - else if (relativeOffset > 0) - { - return SeekForward(relativeOffset); - } - else - { - return SeekBackwards(-relativeOffset); - } - } - - private long SeekForward(long origOffset) - { - long offset = origOffset; - var seekBackBufferLength = _seekBackBuffer.Length; - - int backwardSoughtBytes = _seekBackBufferCount - _seekBackBufferIndex; - int seekForwardInBackBuffer = (int)Math.Min(offset, backwardSoughtBytes); - offset -= seekForwardInBackBuffer; - _seekBackBufferIndex += seekForwardInBackBuffer; - - if (offset > 0) - { - // first completely fill seekBackBuffer to remove special cases from while loop below - if (_seekBackBufferCount < seekBackBufferLength) - { - var maxRead = seekBackBufferLength - _seekBackBufferCount; - if (offset < maxRead) - { - maxRead = (int)offset; - } - - var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead); - _underlyingPosition += bytesRead; - _seekBackBufferCount += bytesRead; - _seekBackBufferIndex = _seekBackBufferCount; - if (bytesRead < maxRead) - { - if (_seekBackBufferCount < offset) - { - throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes"); - } - - return Position; - } - offset -= bytesRead; - } - - // now alternate between filling tempBuffer and seekBackBuffer - bool fillTempBuffer = true; - var tempBuffer = new byte[seekBackBufferLength]; - while (offset > 0) - { - var maxRead = offset < seekBackBufferLength ? (int)offset : seekBackBufferLength; - var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, maxRead); - _underlyingPosition += bytesRead; - var bytesReadDiff = maxRead - bytesRead; - offset -= bytesRead; - if (bytesReadDiff > 0 /* reached end-of-stream */ || offset == 0) - { - if (fillTempBuffer) - { - if (bytesRead > 0) - { - Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); - Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); - } - } - else - { - if (bytesRead > 0) - { - Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); - } - - Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); - } - if (offset > 0) - { - throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes"); - } - } - fillTempBuffer = !fillTempBuffer; - } - } - return Position; - } - - private long SeekBackwards(long offset) - { - var intOffset = (int)offset; - if (offset > int.MaxValue || intOffset > _seekBackBufferIndex) - { - throw new NotSupportedException("Cannot currently seek backwards more than " + _seekBackBufferIndex + " bytes"); - } - - _seekBackBufferIndex -= intOffset; - return Position; - } - - private long SeekFromEnd(long offset) - { - var intOffset = (int)offset; - var seekBackBufferLength = _seekBackBuffer.Length; - if (offset > int.MaxValue || intOffset > seekBackBufferLength) - { - throw new NotSupportedException("Cannot seek backwards from end more than " + seekBackBufferLength + " bytes"); - } - - // first completely fill seekBackBuffer to remove special cases from while loop below - if (_seekBackBufferCount < seekBackBufferLength) - { - var maxRead = seekBackBufferLength - _seekBackBufferCount; - var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead); - _underlyingPosition += bytesRead; - _seekBackBufferCount += bytesRead; - _seekBackBufferIndex = Math.Max(0, _seekBackBufferCount - intOffset); - if (bytesRead < maxRead) - { - if (_seekBackBufferCount < intOffset) - { - throw new NotSupportedException("Could not seek backwards from end " + intOffset + " bytes"); - } - - return Position; - } - } - else - { - _seekBackBufferIndex = _seekBackBufferCount; - } - - // now alternate between filling tempBuffer and seekBackBuffer - bool fillTempBuffer = true; - var tempBuffer = new byte[seekBackBufferLength]; - while (true) - { - var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, seekBackBufferLength); - _underlyingPosition += bytesRead; - var bytesReadDiff = seekBackBufferLength - bytesRead; - if (bytesReadDiff > 0) // reached end-of-stream - { - if (fillTempBuffer) - { - if (bytesRead > 0) - { - Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); - Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); - } - } - else - { - if (bytesRead > 0) - { - Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); - } - - Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); - } - _seekBackBufferIndex -= intOffset; - return Position; - } - fillTempBuffer = !fillTempBuffer; - } - } - - public override long Position - { - get { return _underlyingPosition - (_seekBackBufferCount - _seekBackBufferIndex); } - set { Seek(value, SeekOrigin.Begin); } - } - - public override bool CanTimeout { get { return _underlyingStream.CanTimeout; } } - public override bool CanWrite { get { return _underlyingStream.CanWrite; } } - public override long Length { get { return _underlyingStream.Length; } } - public override void SetLength(long value) { _underlyingStream.SetLength(value); } - public override void Write(byte[] buffer, int offset, int count) { _underlyingStream.Write(buffer, offset, count); } - public override void Flush() { _underlyingStream.Flush(); } - public override void Close() { _underlyingStream.Close(); } - public new void Dispose() { _underlyingStream.Dispose(); } - protected override void Dispose(bool disposing) - { - if (disposing) - { - _underlyingStream.Dispose(); - } - } - } - - // For reading a compressed stream or normal stream - internal class DecompressedStream : Stream - { - private readonly Stream UnderlyingStream; - private readonly bool IsSourceCompressed; - private readonly UInt64 DecompressedLength; - private Int64 ReadPosition = 0; - - // For reading a compressed stream - internal DecompressedStream(Stream InputStream) - { - UnderlyingStream = new ReadSeekableStream(InputStream, 0x100); - - byte[] Signature = new byte["CompressedPartition".Length + 2]; - Signature[0x00] = 0xFF; - Buffer.BlockCopy(Encoding.ASCII.GetBytes("CompressedPartition"), 0, Signature, 0x01, "CompressedPartition".Length); - Signature["CompressedPartition".Length + 1] = 0x00; - - int PrimaryHeaderSize = 0x0A + "CompressedPartition".Length; - byte[] SignatureRead = new byte[Signature.Length]; - UnderlyingStream.Read(SignatureRead, 0, Signature.Length); - - IsSourceCompressed = StructuralComparisons.StructuralEqualityComparer.Equals(Signature, SignatureRead); - if (IsSourceCompressed) - { - byte[] FormatVersionBytes = new byte[4]; - UnderlyingStream.Read(FormatVersionBytes, 0, 4); - if (BitConverter.ToUInt32(FormatVersionBytes, 0) > 1) // Max supported format version = 1 - { - throw new InvalidDataException(); - } - - byte[] HeaderSizeBytes = new byte[4]; - UnderlyingStream.Read(HeaderSizeBytes, 0, 4); - UInt32 HeaderSize = BitConverter.ToUInt32(HeaderSizeBytes, 0); - - if (HeaderSize >= (Signature.Length + 0x10)) - { - byte[] DecompressedLengthBytes = new byte[8]; - UnderlyingStream.Read(DecompressedLengthBytes, 0, 8); - DecompressedLength = BitConverter.ToUInt64(DecompressedLengthBytes, 0); - } - else - { - throw new InvalidDataException(); - } - - UInt32 HeaderBytesRemaining = (UInt32)(HeaderSize - Signature.Length - 0x10); - if (HeaderBytesRemaining > 0) - { - byte[] HeaderBytes = new byte[HeaderBytesRemaining]; - UnderlyingStream.Read(HeaderBytes, 0, (int)HeaderBytesRemaining); - } - - UnderlyingStream = new GZipStream(UnderlyingStream, CompressionMode.Decompress, false); - } - else - { - UnderlyingStream.Position = 0; - } - } - - public override bool CanRead { get { return true; } } - public override bool CanSeek { get { return false; } } - public override int Read(byte[] buffer, int offset, int count) - { - int RealCount = UnderlyingStream.Read(buffer, offset, count); - ReadPosition += RealCount; - return RealCount; - } - public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } - public override long Position - { - get { return ReadPosition; } - set { throw new NotSupportedException(); } - } - public override bool CanTimeout { get { return UnderlyingStream.CanTimeout; } } - public override bool CanWrite { get { return true; } } - public override long Length - { - get - { - if (IsSourceCompressed) - { - return (long)DecompressedLength; - } - else - { - return UnderlyingStream.Length; - } - } - } - public override void SetLength(long value) { throw new NotSupportedException(); } - public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } - public override void Flush() { UnderlyingStream.Flush(); } - public override void Close() { UnderlyingStream.Close(); } - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.UnderlyingStream.Dispose(); - } - } - } - - // For writing a compressed stream - internal class CompressedStream : Stream - { - private readonly UInt32 HeaderSize; - private UInt64 WritePosition; - private readonly GZipStream UnderlyingStream; - - internal CompressedStream(Stream OutputStream, UInt64 TotalDecompressedStreamLength) - { - // Write header - HeaderSize = (UInt32)(0x12 + "CompressedPartition".Length); - OutputStream.WriteByte(0xFF); - OutputStream.Write(Encoding.ASCII.GetBytes("CompressedPartition"), 0, "CompressedPartition".Length); - OutputStream.WriteByte(0x00); - OutputStream.Write(BitConverter.GetBytes((UInt32)1), 0, 4); // Format version = 1 - OutputStream.Write(BitConverter.GetBytes(HeaderSize), 0, 4); // Headersize - OutputStream.Write(BitConverter.GetBytes(TotalDecompressedStreamLength), 0, 8); - - this.UnderlyingStream = new GZipStream(OutputStream, CompressionLevel.Optimal, false); - WritePosition = 0; - } - - public override bool CanRead { get { return false; } } - public override bool CanSeek { get { return false; } } - public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } - public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } - public override long Position - { - get { return (long)WritePosition; } - set { throw new NotSupportedException(); } - } - public override bool CanTimeout { get { return UnderlyingStream.CanTimeout; } } - public override bool CanWrite { get { return true; } } - public override long Length { get { return (long)WritePosition; } } - public override void SetLength(long value) { throw new NotSupportedException(); } - public override void Write(byte[] buffer, int offset, int count) - { - WritePosition += (UInt64)count; - UnderlyingStream.Write(buffer, offset, count); - } - public override void Flush() { UnderlyingStream.Flush(); } - public override void Close() { UnderlyingStream.Close(); } - public new void Dispose() { UnderlyingStream.Dispose(); } - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.UnderlyingStream.Dispose(); - } - } - } - - internal class SeekableStream : Stream - { - private Stream UnderlyingStream; - private Int64 ReadPosition = 0; - private readonly Func StreamInitializer; - private readonly Int64 UnderlyingStreamLength; - - // For reading a compressed stream - internal SeekableStream(Func StreamInitializer, Int64? Length = null) - { - this.StreamInitializer = StreamInitializer; - UnderlyingStream = StreamInitializer(); - if (Length != null) - { - UnderlyingStreamLength = (Int64)Length; - } - else - { - try - { - UnderlyingStreamLength = UnderlyingStream.Length; - } - catch - { - throw new ArgumentException("Unknown stream length"); - } - } - } - - public override bool CanRead { get { return true; } } - public override bool CanSeek { get { return true; } } - public override int Read(byte[] buffer, int offset, int count) - { - int RealCount = UnderlyingStream.Read(buffer, offset, count); - ReadPosition += RealCount; - return RealCount; - } - public override long Seek(long offset, SeekOrigin origin) - { - if (UnderlyingStream.CanSeek) - { - ReadPosition = UnderlyingStream.Seek(offset, origin); - return ReadPosition; - } - else - { - Int64 NewPosition = 0; - switch (origin) - { - case SeekOrigin.Begin: - NewPosition = offset; - break; - case SeekOrigin.Current: - NewPosition = ReadPosition + offset; - break; - case SeekOrigin.End: - NewPosition = UnderlyingStreamLength - offset; - break; - } - if ((NewPosition < 0) || (NewPosition > UnderlyingStreamLength)) - { - throw new ArgumentOutOfRangeException(); - } - - if (NewPosition < ReadPosition) - { - UnderlyingStream.Close(); - UnderlyingStream = StreamInitializer(); - ReadPosition = 0; - } - UInt64 Remaining; - byte[] Buffer = new byte[16384]; - while (ReadPosition < NewPosition) - { - Remaining = (UInt64)(NewPosition - ReadPosition); - if (Remaining > (UInt64)Buffer.Length) - { - Remaining = (UInt64)Buffer.Length; - } - - UnderlyingStream.Read(Buffer, 0, (int)Remaining); - ReadPosition += (long)Remaining; - } - return ReadPosition; - } - } - public override long Position - { - get - { - return ReadPosition; - } - set - { - Seek(value, SeekOrigin.Begin); - } - } - public override bool CanTimeout { get { return UnderlyingStream.CanTimeout; } } - public override bool CanWrite { get { return false; } } - public override long Length - { - get - { - return UnderlyingStreamLength; - } - } - public override void SetLength(long value) { throw new NotSupportedException(); } - public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } - public override void Flush() { throw new NotSupportedException(); } - public override void Close() { UnderlyingStream.Close(); } - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.UnderlyingStream.Dispose(); - } - } - } - - internal enum ResourceType - { - RT_CURSOR = 1, - RT_BITMAP = 2, - RT_ICON = 3, - RT_MENU = 4, - RT_DIALOG = 5, - RT_STRING = 6, - RT_FONTDIR = 7, - RT_FONT = 8, - RT_ACCELERATOR = 9, - RT_RCDATA = 10, - RT_MESSAGETABLE = 11, - RT_GROUP_CURSOR = RT_CURSOR + 11, - RT_GROUP_ICON = RT_ICON + 11, - RT_VERSION = 16, - RT_DLGINCLUDE = 17, - RT_PLUGPLAY = 19, - RT_VXD = 20, - RT_ANICURSOR = 21, - RT_ANIICON = 22, - RT_HTML = 23, - RT_MANIFEST = 24, - RT_DLGINIT = 240, - RT_TOOLBAR = 241 - }; - - internal static class PE - { - internal static byte[] GetResource(byte[] PEfile, int[] Index) - { - // Explanation of PE header here: - // https://msdn.microsoft.com/en-us/library/ms809762.aspx?f=255&MSPPError=-2147217396 - - UInt32 PEPointer = ByteOperations.ReadUInt32(PEfile, 0x3C); - UInt16 OptionalHeaderSize = ByteOperations.ReadUInt16(PEfile, PEPointer + 0x14); - UInt32 SectionTablePointer = PEPointer + 0x18 + OptionalHeaderSize; - UInt16 SectionCount = ByteOperations.ReadUInt16(PEfile, PEPointer + 0x06); - UInt32? ResourceSectionEntryPointer = null; - for (int i = 0; i < SectionCount; i++) - { - string SectionName = ByteOperations.ReadAsciiString(PEfile, (UInt32)(SectionTablePointer + (i * 0x28)), 8); - int e = SectionName.IndexOf('\0'); - if (e >= 0) - { - SectionName = SectionName.Substring(0, e); - } - - if (SectionName == ".rsrc") - { - ResourceSectionEntryPointer = (UInt32)(SectionTablePointer + (i * 0x28)); - break; - } - } - if (ResourceSectionEntryPointer == null) - { - throw new WPinternalsException("Resource-section not found"); - } - - UInt32 ResourceRawSize = ByteOperations.ReadUInt32(PEfile, (UInt32)ResourceSectionEntryPointer + 0x10); - UInt32 ResourceRawPointer = ByteOperations.ReadUInt32(PEfile, (UInt32)ResourceSectionEntryPointer + 0x14); - UInt32 ResourceVirtualPointer = ByteOperations.ReadUInt32(PEfile, (UInt32)ResourceSectionEntryPointer + 0x0C); - - UInt32 p = ResourceRawPointer; - for (int i = 0; i < Index.Length; i++) - { - UInt16 ResourceNamedEntryCount = ByteOperations.ReadUInt16(PEfile, p + 0x0c); - UInt16 ResourceIdEntryCount = ByteOperations.ReadUInt16(PEfile, p + 0x0e); - for (int j = ResourceNamedEntryCount; j < ResourceNamedEntryCount + ResourceIdEntryCount; j++) - { - UInt32 ResourceID = ByteOperations.ReadUInt32(PEfile, (UInt32)(p + 0x10 + (j * 8))); - UInt32 NextPointer = ByteOperations.ReadUInt32(PEfile, (UInt32)(p + 0x10 + (j * 8) + 4)); - if (ResourceID == (UInt32)Index[i]) - { - // Check high bit - if ((NextPointer & 0x80000000) == 0 != (i == (Index.Length - 1))) - { - throw new WPinternalsException("Bad resource path"); - } - - p = ResourceRawPointer + (NextPointer & 0x7fffffff); - break; - } - } - } - - UInt32 ResourceValuePointer = ByteOperations.ReadUInt32(PEfile, p) - ResourceVirtualPointer + ResourceRawPointer; - UInt32 ResourceValueSize = ByteOperations.ReadUInt32(PEfile, p + 4); - - byte[] ResourceValue = new byte[ResourceValueSize]; - Array.Copy(PEfile, ResourceValuePointer, ResourceValue, 0, ResourceValueSize); - - return ResourceValue; - } - - internal static Version GetFileVersion(byte[] PEfile) - { - byte[] version = GetResource(PEfile, new int[] { (int)ResourceType.RT_VERSION, 1, 1033 }); - - // RT_VERSION format: - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001(v=vs.85).aspx - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms646997(v=vs.85).aspx - const UInt32 FixedFileInfoPointer = 0x28; - UInt16 Major = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0A); - UInt16 Minor = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x08); - UInt16 Build = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0E); - UInt16 Revision = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0C); - - return new Version(Major, Minor, Build, Revision); - } - - internal static Version GetProductVersion(byte[] PEfile) - { - byte[] version = GetResource(PEfile, new int[] { (int)ResourceType.RT_VERSION, 1, 1033 }); - - // RT_VERSION format: - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001(v=vs.85).aspx - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms646997(v=vs.85).aspx - const UInt32 FixedFileInfoPointer = 0x28; - UInt16 Major = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x12); - UInt16 Minor = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x10); - UInt16 Build = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x16); - UInt16 Revision = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x14); - - return new Version(Major, Minor, Build, Revision); - } - } - -#if PREVIEW - internal static class Uploader - { - internal static List Uploads = new List(); - - internal static void Upload(string FileName, string Text) - { - byte[] byteArray = Encoding.UTF8.GetBytes(Text); - MemoryStream FileStream = new MemoryStream(byteArray); - Upload(FileName, FileStream); - } - - internal static void Upload(string FileName, byte[] Data) - { - Upload(FileName, new MemoryStream(Data)); - } - - internal static void Upload(string FileName, Stream FileStream) - { - Upload(new Uri(@"https://www.wpinternals.net/upload.php", UriKind.Absolute), "uploadedfile", FileName, FileStream); - } - - private static void Upload(Uri Address, string InputName, string FileName, Stream FileStream) - { - System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient(); - System.Net.Http.MultipartFormDataContent form = new System.Net.Http.MultipartFormDataContent(); - System.Net.Http.StreamContent Content = new System.Net.Http.StreamContent(FileStream); - Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/plain"); - form.Add(Content, InputName, FileName); - Task UploadTask = httpClient.PostAsync(Address, form); - - Uploads.Add( - UploadTask.ContinueWith((t) => - { - Uploads.Remove(t); - httpClient.Dispose(); - }) - ); - } - - internal static void WaitForUploads() - { - Task.WaitAll(Uploads.ToArray()); - } - } -#endif - - internal class AsyncAutoResetEvent - { - private readonly LinkedList> waiters = - new(); - - private bool isSignaled; - - public AsyncAutoResetEvent(bool signaled) - { - this.isSignaled = signaled; - } - - public Task WaitAsync(TimeSpan timeout) - { - return this.WaitAsync(timeout, CancellationToken.None); - } - - public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) - { - TaskCompletionSource tcs; - - lock (this.waiters) - { - if (this.isSignaled) - { - this.isSignaled = false; - return true; - } - else if (timeout == TimeSpan.Zero) - { - return this.isSignaled; - } - else - { - tcs = new TaskCompletionSource(); - this.waiters.AddLast(tcs); - } - } - - Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)); - if (winner == tcs.Task) - { - // The task was signaled. - return true; - } - else - { - // We timed-out; remove our reference to the task. - // This is an O(n) operation since waiters is a LinkedList. - lock (this.waiters) - { - bool removed = this.waiters.Remove(tcs); - System.Diagnostics.Debug.Assert(removed); - return false; - } - } - } - - public void Set() - { - TaskCompletionSource toRelease = null; - - lock (this.waiters) - { - if (this.waiters.Count > 0) - { - // Signal the first task in the waiters list. - toRelease = this.waiters.First.Value; - this.waiters.RemoveFirst(); - } - else if (!this.isSignaled) - { - // No tasks are pending - this.isSignaled = true; - } - } - - toRelease?.SetResult(true); - } - } - - // This class was written by: Rolf Wessels - // https://github.com/rolfwessels/lazycowprojects/tree/master/Wpf - - /// - /// Static class used to attach to wpf control - /// - public static class GridViewColumnResize - { - #region DependencyProperties - - public static readonly DependencyProperty WidthProperty = - DependencyProperty.RegisterAttached("Width", typeof(string), typeof(GridViewColumnResize), - new PropertyMetadata(OnSetWidthCallback)); - - public static readonly DependencyProperty GridViewColumnResizeBehaviorProperty = - DependencyProperty.RegisterAttached("GridViewColumnResizeBehavior", - typeof(GridViewColumnResizeBehavior), typeof(GridViewColumnResize), - null); - - public static readonly DependencyProperty EnabledProperty = - DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(GridViewColumnResize), - new PropertyMetadata(OnSetEnabledCallback)); - - public static readonly DependencyProperty ListViewResizeBehaviorProperty = - DependencyProperty.RegisterAttached("ListViewResizeBehaviorProperty", - typeof(ListViewResizeBehavior), typeof(GridViewColumnResize), null); - - #endregion - - public static string GetWidth(DependencyObject obj) - { - return (string)obj.GetValue(WidthProperty); - } - - public static void SetWidth(DependencyObject obj, string value) - { - obj.SetValue(WidthProperty, value); - } - - public static bool GetEnabled(DependencyObject obj) - { - return (bool)obj.GetValue(EnabledProperty); - } - - public static void SetEnabled(DependencyObject obj, bool value) - { - obj.SetValue(EnabledProperty, value); - } - - #region CallBack - - private static void OnSetWidthCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is GridViewColumn element) - { - GridViewColumnResizeBehavior behavior = GetOrCreateBehavior(element); - behavior.Width = e.NewValue as string; - } - else - { - Console.Error.WriteLine("Error: Expected type GridViewColumn but found " + - dependencyObject.GetType().Name); - } - } - - private static void OnSetEnabledCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is ListView element) - { - ListViewResizeBehavior behavior = GetOrCreateBehavior(element); - behavior.Enabled = (bool)e.NewValue; - } - else - { - Console.Error.WriteLine("Error: Expected type ListView but found " + dependencyObject.GetType().Name); - } - } - - private static ListViewResizeBehavior GetOrCreateBehavior(ListView element) - { - if (element.GetValue(GridViewColumnResizeBehaviorProperty) is not ListViewResizeBehavior behavior) - { - behavior = new ListViewResizeBehavior(element); - element.SetValue(ListViewResizeBehaviorProperty, behavior); - } - - return behavior; - } - - private static GridViewColumnResizeBehavior GetOrCreateBehavior(GridViewColumn element) - { - if (element.GetValue(GridViewColumnResizeBehaviorProperty) is not GridViewColumnResizeBehavior behavior) - { - behavior = new GridViewColumnResizeBehavior(element); - element.SetValue(GridViewColumnResizeBehaviorProperty, behavior); - } - - return behavior; - } - - #endregion - - #region Nested type: GridViewColumnResizeBehavior - - // This class was written by: Rolf Wessels - // https://github.com/rolfwessels/lazycowprojects/tree/master/Wpf - - /// - /// GridViewColumn class that gets attached to the GridViewColumn control - /// - public class GridViewColumnResizeBehavior - { - private readonly GridViewColumn _element; - - public GridViewColumnResizeBehavior(GridViewColumn element) - { - _element = element; - } - - public string Width { get; set; } - - public bool IsStatic - { - get { return StaticWidth >= 0; } - } - - public double StaticWidth - { - get - { - return double.TryParse(Width, out double result) ? result : -1; - } - } - - public double Percentage - { - get - { - if (!IsStatic) - { - return Mulitplier * 100; - } - return 0; - } - } - - public double Mulitplier - { - get - { - if (Width == "*" || Width == "1*") - { - return 1; - } - - if (Width.EndsWith("*") && double.TryParse(Width[0..^1], out double perc)) - { - return perc; - } - return 1; - } - } - - public void SetWidth(double allowedSpace, double totalPercentage) - { - if (IsStatic) - { - _element.Width = StaticWidth; - } - else - { - _element.Width = (double)Math.Max(allowedSpace * (Percentage / totalPercentage), 0); - } - } - } - - #endregion - - #region Nested type: ListViewResizeBehavior - - // This class was written by: Rolf Wessels - // https://github.com/rolfwessels/lazycowprojects/tree/master/Wpf - - /// - /// ListViewResizeBehavior class that gets attached to the ListView control - /// - public class ListViewResizeBehavior - { - private const int Margin = 25; - private const long RefreshTime = Timeout.Infinite; - private const long Delay = 500; - - private readonly ListView _element; - private readonly Timer _timer; - - public ListViewResizeBehavior(ListView element) - { - _element = element ?? throw new ArgumentNullException(nameof(element)); - element.Loaded += OnLoaded; - - // Action for resizing and re-enable the size lookup - // This stops the columns from constantly resizing to improve performance - Action resizeAndEnableSize = () => - { - Resize(); - _element.SizeChanged += OnSizeChanged; - }; - _timer = new Timer(x => Application.Current.Dispatcher.BeginInvoke(resizeAndEnableSize), null, Delay, - RefreshTime); - } - - public bool Enabled { get; set; } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - _element.SizeChanged += OnSizeChanged; - } - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) - { - if (e.WidthChanged) - { - _element.SizeChanged -= OnSizeChanged; - _timer.Change(Delay, RefreshTime); - } - } - - private void Resize() - { - if (Enabled) - { - double totalWidth = _element.ActualWidth; - if (_element.View is GridView gv) - { - double allowedSpace = totalWidth - GetAllocatedSpace(gv); - allowedSpace -= Margin; - double totalPercentage = GridViewColumnResizeBehaviors(gv).Sum(x => x.Percentage); - foreach (GridViewColumnResizeBehavior behavior in GridViewColumnResizeBehaviors(gv)) - { - behavior.SetWidth(allowedSpace, totalPercentage); - } - } - } - } - - private static IEnumerable GridViewColumnResizeBehaviors(GridView gv) - { - foreach (GridViewColumn t in gv.Columns) - { - if (t.GetValue(GridViewColumnResizeBehaviorProperty) is GridViewColumnResizeBehavior gridViewColumnResizeBehavior) - { - yield return gridViewColumnResizeBehavior; - } - } - } - - private static double GetAllocatedSpace(GridView gv) - { - double totalWidth = 0; - foreach (GridViewColumn t in gv.Columns) - { - if (t.GetValue(GridViewColumnResizeBehaviorProperty) is GridViewColumnResizeBehavior gridViewColumnResizeBehavior) - { - if (gridViewColumnResizeBehavior.IsStatic) - { - totalWidth += gridViewColumnResizeBehavior.StaticWidth; - } - } - else - { - totalWidth += t.ActualWidth; - } - } - return totalWidth; - } - } - - #endregion - } - - internal static class ExtensionMethods - { - // This method was written by: Lawrence Johnston - // https://stackoverflow.com/a/22078975 - public static async Task TimeoutAfter(this Task task, TimeSpan timeout) - { - using var timeoutCancellationTokenSource = new CancellationTokenSource(); - var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)); - if (completedTask == task) - { - timeoutCancellationTokenSource.Cancel(); - return await task; // Very important in order to propagate exceptions - } - else - { - throw new TimeoutException("The operation has timed out."); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Some of the classes and functions in this file were found online. +// Where possible the original authors are referenced. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Threading; + +namespace WPinternals +{ + internal delegate void SetWorkingStatus(string Message, string SubMessage = null, ulong? MaxProgressValue = null, bool ShowAnimation = true, WPinternalsStatus Status = WPinternalsStatus.Undefined); + internal delegate void UpdateWorkingStatus(string Message, string SubMessage = null, ulong? CurrentProgressValue = null, WPinternalsStatus Status = WPinternalsStatus.Undefined); + internal delegate void ExitSuccess(string Message, string SubMessage = null); + internal delegate void ExitFailure(string Message, string SubMessage = null); + + internal enum WPinternalsStatus + { + Undefined, + Scanning, + Flashing, + Patching, + WaitingForManualReset, + SwitchingMode, + Initializing + }; + + public class BooleanConverter : DependencyObject, IValueConverter + { + public static readonly DependencyProperty OnTrueProperty = + DependencyProperty.Register("OnTrue", typeof(object), typeof(BooleanConverter), + new PropertyMetadata(default(object))); + + public static readonly DependencyProperty OnFalseProperty = + DependencyProperty.Register("OnFalse", typeof(object), typeof(BooleanConverter), + new PropertyMetadata(default(object))); + + public static readonly DependencyProperty OnNullProperty = + DependencyProperty.Register("OnNull", typeof(object), typeof(BooleanConverter), + new PropertyMetadata(default(object))); + + public object OnTrue + { + get { return GetValue(OnTrueProperty); } + set { SetValue(OnTrueProperty, value); } + } + + public object OnFalse + { + get { return GetValue(OnFalseProperty); } + set { SetValue(OnFalseProperty, value); } + } + + public object OnNull + { + get { return GetValue(OnNullProperty); } + set { SetValue(OnNullProperty, value); } + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null + ? OnNull ?? Default(targetType) + : (string.Equals(value.ToString(), false.ToString(), StringComparison.CurrentCultureIgnoreCase) + ? OnFalse + : OnTrue); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == OnNull) + { + return Default(targetType); + } + + if (value == OnFalse) + { + return false; + } + + if (value == OnTrue) + { + return true; + } + + if (value == null) + { + return null; + } + + if (OnNull != null && + string.Equals(value.ToString(), OnNull.ToString(), StringComparison.CurrentCultureIgnoreCase)) + { + return Default(targetType); + } + + if (OnFalse != null && + string.Equals(value.ToString(), OnFalse.ToString(), StringComparison.CurrentCultureIgnoreCase)) + { + return false; + } + + if (OnTrue != null && + string.Equals(value.ToString(), OnTrue.ToString(), StringComparison.CurrentCultureIgnoreCase)) + { + return true; + } + + return null; + } + + public static object Default(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + } + + public class HexConverter : DependencyObject, IValueConverter + { + public static readonly DependencyProperty SeparatorProperty = + DependencyProperty.Register("OnTrue", typeof(object), typeof(HexConverter), + new PropertyMetadata(" ")); + + public object Separator + { + get { return GetValue(SeparatorProperty); } + set { SetValue(SeparatorProperty, value); } + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is byte[] bytes) + { + StringBuilder s = new(1000); + for (int i = bytes.GetLowerBound(0); i <= bytes.GetUpperBound(0); i++) + { + if (i != bytes.GetLowerBound(0)) + { + s.Append(Separator); + } + + s.Append(bytes[i].ToString("X2")); + } + return s.ToString(); + } + else + { + return ""; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public static object Default(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + } + + public class ObjectToVisibilityConverter : DependencyObject, IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return "Collapsed"; + } + else + { + return "Visible"; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public static object Default(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + } + + public class InverseObjectToVisibilityConverter : DependencyObject, IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return "Visible"; + } + else + { + return "Collapsed"; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public static object Default(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + } + + internal class CollapsibleRun : Run + { + private string CollapsibleText; + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + + CollapsibleText = Text; + } + + public Boolean IsVisible + { + get + { + return (Boolean)this.GetValue(IsVisibleProperty); + } + set + { + this.SetValue(IsVisibleProperty, value); + } + } + public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.Register( + "IsVisible", typeof(Boolean), typeof(CollapsibleRun), new PropertyMetadata(true, IsVisibleChanged)); + public static void IsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((CollapsibleRun)d).Text = (bool)e.NewValue ? ((CollapsibleRun)d).CollapsibleText : String.Empty; + } + } + + internal class CollapsibleSection : Section + { + public CollapsibleSection() + { + CollapsibleBlocks = new List(); + } + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + + foreach (Block Block in Blocks) + { + CollapsibleBlocks.Add(Block); + } + + Blocks.Clear(); + } + + public List CollapsibleBlocks { get; } + + public Boolean IsCollapsed + { + get + { + return (Boolean)this.GetValue(IsCollapsedProperty); + } + set + { + this.SetValue(IsCollapsedProperty, value); + + Blocks.Clear(); + + if (IsInitialized && !value) + { + foreach (Block Block in CollapsibleBlocks) + { + Blocks.Add(Block); + } + } + } + } + public static readonly DependencyProperty IsCollapsedProperty = DependencyProperty.Register( + "IsCollapsed", typeof(Boolean), typeof(CollapsibleSection), new PropertyMetadata(false)); + } + + // Overloaded Paragraph class to remove empty Run-elements, caused by auto-formatting new-lines in the XAML + // Use local:Paragraph in a FlowDocument + // This correction only works at run-time + public class Paragraph : System.Windows.Documents.Paragraph + { + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + int inlinesCount = this.Inlines.Count; + for (int i = 0; i < inlinesCount; i++) + { + Inline inline = this.Inlines.ElementAt(i); + if (inline is Run run) + { + if (run.Text == Convert.ToChar(32).ToString()) //ACSII 32 is the white space + { + run.Text = string.Empty; + } + } + } + } + } + + internal class ArrivalEventArgs : EventArgs + { + public PhoneInterfaces NewInterface; + public IDisposable NewModel; + + public ArrivalEventArgs(PhoneInterfaces NewInterface, IDisposable NewModel) + : base() + { + this.NewInterface = NewInterface; + this.NewModel = NewModel; + } + } + + internal static class BigEndian + { + public static byte[] GetBytes(object Value) + { + byte[] Bytes; + if (Value is short) + { + Bytes = BitConverter.GetBytes((short)Value); + } + else if (Value is ushort) + { + Bytes = BitConverter.GetBytes((ushort)Value); + } + else if (Value is int) + { + Bytes = BitConverter.GetBytes((int)Value); + } + else + { + Bytes = Value is uint ? BitConverter.GetBytes((uint)Value) : throw new NotSupportedException(); + } + + byte[] Result = new byte[Bytes.Length]; + for (int i = 0; i < Bytes.Length; i++) + { + Result[i] = Bytes[Bytes.Length - 1 - i]; + } + + return Result; + } + + public static byte[] GetBytes(object Value, int Width) + { + byte[] Result; + byte[] BigEndianBytes = GetBytes(Value); + if (BigEndianBytes.Length == Width) + { + return BigEndianBytes; + } + else if (BigEndianBytes.Length > Width) + { + Result = new byte[Width]; + Buffer.BlockCopy(BigEndianBytes, BigEndianBytes.Length - Width, Result, 0, Width); + return Result; + } + else + { + Result = new byte[Width]; + Buffer.BlockCopy(BigEndianBytes, 0, Result, Width - BigEndianBytes.Length, BigEndianBytes.Length); + return Result; + } + } + + public static UInt16 ToUInt16(byte[] Buffer, int Offset) + { + byte[] Bytes = new byte[2]; + for (int i = 0; i < 2; i++) + { + Bytes[i] = Buffer[Offset + 1 - i]; + } + + return BitConverter.ToUInt16(Bytes, 0); + } + + public static Int16 ToInt16(byte[] Buffer, int Offset) + { + byte[] Bytes = new byte[2]; + for (int i = 0; i < 2; i++) + { + Bytes[i] = Buffer[Offset + 1 - i]; + } + + return BitConverter.ToInt16(Bytes, 0); + } + + public static UInt32 ToUInt32(byte[] Buffer, int Offset) + { + byte[] Bytes = new byte[4]; + for (int i = 0; i < 4; i++) + { + Bytes[i] = Buffer[Offset + 3 - i]; + } + + return BitConverter.ToUInt32(Bytes, 0); + } + + public static Int32 ToInt32(byte[] Buffer, int Offset) + { + byte[] Bytes = new byte[4]; + for (int i = 0; i < 4; i++) + { + Bytes[i] = Buffer[Offset + 3 - i]; + } + + return BitConverter.ToInt32(Bytes, 0); + } + } + + // This class was found online. + // Original author is probably: mdm20 + // https://stackoverflow.com/questions/5566330/get-gif-to-play-in-wpf-with-gifimage-class/5568703#5568703 + internal class GifImage : Image + { + private bool _isInitialized; + private GifBitmapDecoder _gifDecoder; + private Int32Animation _animation; + + public int FrameIndex + { + get { return (int)GetValue(FrameIndexProperty); } + set { SetValue(FrameIndexProperty, value); } + } + + private void Initialize() + { + _gifDecoder = new GifBitmapDecoder(new Uri("pack://application:,,," + this.GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); + _animation = new Int32Animation(0, _gifDecoder.Frames.Count - 1, new Duration(new TimeSpan(0, 0, 0, _gifDecoder.Frames.Count / 10, (int)(((_gifDecoder.Frames.Count / 10.0) - (_gifDecoder.Frames.Count / 10)) * 1000)))) + { + RepeatBehavior = RepeatBehavior.Forever + }; + this.Source = _gifDecoder.Frames[0]; + + _isInitialized = true; + } + + static GifImage() + { + VisibilityProperty.OverrideMetadata(typeof(GifImage), + new FrameworkPropertyMetadata(VisibilityPropertyChanged)); + } + + private static void VisibilityPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if ((Visibility)e.NewValue == Visibility.Visible) + { + ((GifImage)sender).StartAnimation(); + } + else + { + ((GifImage)sender).StopAnimation(); + } + } + + public static readonly DependencyProperty FrameIndexProperty = + DependencyProperty.Register("FrameIndex", typeof(int), typeof(GifImage), new UIPropertyMetadata(0, new PropertyChangedCallback(ChangingFrameIndex))); + + private static void ChangingFrameIndex(DependencyObject obj, DependencyPropertyChangedEventArgs ev) + { + var gifImage = obj as GifImage; + gifImage.Source = gifImage._gifDecoder.Frames[(int)ev.NewValue]; + } + + public bool AutoStart + { + get { return (bool)GetValue(AutoStartProperty); } + set { SetValue(AutoStartProperty, value); } + } + + public static readonly DependencyProperty AutoStartProperty = + DependencyProperty.Register("AutoStart", typeof(bool), typeof(GifImage), new UIPropertyMetadata(false, AutoStartPropertyChanged)); + + private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if ((bool)e.NewValue) + { + (sender as GifImage)?.StartAnimation(); + } + } + + public string GifSource + { + get { return (string)GetValue(GifSourceProperty); } + set { SetValue(GifSourceProperty, value); } + } + + public static readonly DependencyProperty GifSourceProperty = + DependencyProperty.Register("GifSource", typeof(string), typeof(GifImage), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged)); + + private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + (sender as GifImage)?.Initialize(); + } + + public void StartAnimation() + { + if (!_isInitialized) + { + this.Initialize(); + } + + BeginAnimation(FrameIndexProperty, _animation); + } + + public void StopAnimation() + { + BeginAnimation(FrameIndexProperty, null); + } + } + + internal enum LogType + { + FileOnly, + FileAndConsole, + ConsoleOnly + }; + + internal static class LogFile + { + private static readonly StreamWriter w = null; + private static readonly object lockobject = new(); +#if PREVIEW + private static string LogAction = null; + private static StringBuilder LogBuilder; +#endif + + static LogFile() + { + try + { + if (!Directory.Exists(Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals"))) + { + Directory.CreateDirectory(Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals")); + } + + w = File.AppendText(Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\WPInternals.log")); + } + catch { } + } + + public static void Log(string logMessage, LogType Type = LogType.FileOnly) + { + if (w == null) + { + return; + } + + lock (lockobject) + { + if ((Type == LogType.FileOnly) || (Type == LogType.FileAndConsole)) + { + DateTime Now = DateTime.Now; + string Text = Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + ": " + logMessage; + w.WriteLine(Text); + w.Flush(); + +#if PREVIEW + if (LogAction != null) + LogBuilder.AppendLine(Text); +#endif + } + + if (CommandLine.IsConsoleVisible && ((Type == LogType.ConsoleOnly) || (Type == LogType.FileAndConsole))) + { + Console.WriteLine(logMessage); + } + } + } + + public static void LogException(Exception Ex, LogType Type = LogType.FileAndConsole, string AdditionalInfo = null) + { + string Indent = ""; + Exception CurrentEx = Ex; + while (CurrentEx != null) + { + Log(Indent + "Error: " + RemoveBadChars(CurrentEx.Message).Replace("of type '.' ", "") + (AdditionalInfo == null ? "" : " - " + AdditionalInfo), Type); + AdditionalInfo = null; + if (CurrentEx is WPinternalsException) + { + Log(Indent + ((WPinternalsException)CurrentEx).SubMessage, Type); + } +#if DEBUG + if (CurrentEx.StackTrace != null) + { + Log(Indent + CurrentEx.StackTrace, LogType.FileOnly); + } +#endif + Indent += " "; + CurrentEx = CurrentEx.InnerException; + } + } + + private static string RemoveBadChars(string Text) + { + return System.Text.RegularExpressions.Regex.Replace(Text, @"[^\u0020-\u007E]+", string.Empty); + } + + public static void DumpLog(StreamReader r) + { + string line; + while ((line = r.ReadLine()) != null) + { + Console.WriteLine(line); + } + } + + public static void LogApplicationVersion() + { + Log("Windows Phone Internals version " + + Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Build.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString(), LogType.FileAndConsole); + Log("Copyright Heathcliff74", LogType.FileAndConsole); + } + + internal static void BeginAction(string Action) + { +#if PREVIEW + if (LogAction == null) + { + LogAction = Action; + LogBuilder = new StringBuilder(); + LogBuilder.AppendLine("Windows Phone Internals version " + + Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Build.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString()); + LogBuilder.AppendLine("Copyright Heathcliff74"); + LogBuilder.AppendLine("Action: " + Action); + if (App.Config.RegistrationName != null) + LogBuilder.AppendLine("Name: " + App.Config.RegistrationName); + if (App.Config.RegistrationEmail != null) + LogBuilder.AppendLine("Mail: " + App.Config.RegistrationEmail); + if (App.Config.RegistrationSkypeID != null) + LogBuilder.AppendLine("Skype: " + App.Config.RegistrationSkypeID); + if (App.Config.RegistrationTelegramID != null) + LogBuilder.AppendLine("Telegram: " + App.Config.RegistrationTelegramID); + if (Environment.MachineName != null) + LogBuilder.AppendLine("Machine: " + Environment.MachineName); + } +#endif + } + + internal static void EndAction() + { + EndAction(null); + } + + internal static void EndAction(string Action) + { +#if PREVIEW + if ((LogAction != null) && ((Action == null) || (LogAction == Action))) + { + Action = LogAction; + LogAction = null; + string FileName = ""; + if (App.Config.RegistrationName != null) + FileName += App.Config.RegistrationName + " - "; + FileName += DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + " - " + Action + " - " + + Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Build.ToString() + "." + + Assembly.GetExecutingAssembly().GetName().Version.Revision.ToString() + ".log"; + + // Normalize filename + try + { + FileName = System.Text.Encoding.ASCII.GetString(System.Text.Encoding.GetEncoding("ISO-8859-8").GetBytes(FileName)); + } + catch { } + FileName = FileName.Replace("?", ""); + + if (Action.ToLower() == "registration") + Uploader.Upload(FileName, LogBuilder.ToString()); + else + { + try + { + Uploader.Upload(FileName, LogBuilder.ToString()); + } + catch { } + } + } +#endif + } + } + + public static class Converter + { + public static string ConvertHexToString(byte[] Bytes, string Separator) + { + StringBuilder s = new(1000); + for (int i = Bytes.GetLowerBound(0); i <= Bytes.GetUpperBound(0); i++) + { + if (i != Bytes.GetLowerBound(0)) + { + s.Append(Separator); + } + + s.Append(Bytes[i].ToString("X2")); + } + return s.ToString(); + } + + public static byte[] ConvertStringToHex(string HexString) + { + if (HexString.Length % 2 == 1) + { + throw new Exception("The binary key cannot have an odd number of digits"); + } + + byte[] arr = new byte[HexString.Length >> 1]; + + for (int i = 0; i < (HexString.Length >> 1); ++i) + { + arr[i] = (byte)((GetHexVal(HexString[i << 1]) << 4) + GetHexVal(HexString[(i << 1) + 1])); + } + + return arr; + } + + public static int GetHexVal(char hex) + { + int val = hex; + //For uppercase A-F letters: + //return val - (val < 58 ? 48 : 55); + //For lowercase a-f letters: + //return val - (val < 58 ? 48 : 87); + //Or the two combined, but a bit slower: + return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); + } + } + + // This class was found online. + // Original author is probably: John Melville + // https://social.msdn.microsoft.com/Forums/en-US/163ef755-ff7b-4ea5-b226-bbe8ef5f4796/is-there-a-pattern-for-calling-an-async-method-synchronously?forum=async + public static class AsyncHelpers + { + /// + /// Execute's an async Task method which has a void return value synchronously + /// + /// Task method to execute + public static void RunSync(Func task) + { + var oldContext = SynchronizationContext.Current; + var synch = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(synch); + synch.Post(async _ => + { + try + { + await task(); + } + catch (Exception e) + { + synch.InnerException = e; + throw; + } + finally + { + synch.EndMessageLoop(); + } + }, null); + synch.BeginMessageLoop(); + + SynchronizationContext.SetSynchronizationContext(oldContext); + } + + /// + /// Execute's an async Task method which has a T return type synchronously + /// + /// Return Type + /// Task method to execute + /// + public static T RunSync(Func> task) + { + var oldContext = SynchronizationContext.Current; + var synch = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(synch); + T ret = default; + synch.Post(async _ => + { + try + { + ret = await task(); + } + catch (Exception e) + { + synch.InnerException = e; + throw; + } + finally + { + synch.EndMessageLoop(); + } + }, null); + synch.BeginMessageLoop(); + SynchronizationContext.SetSynchronizationContext(oldContext); + return ret; + } + + private class ExclusiveSynchronizationContext : SynchronizationContext + { + private bool done; + public Exception InnerException { get; set; } + private readonly AutoResetEvent workItemsWaiting = new(false); + private readonly Queue> items = + new(); + + public override void Send(SendOrPostCallback d, object state) + { + throw new NotSupportedException("We cannot send to our same thread"); + } + + public override void Post(SendOrPostCallback d, object state) + { + lock (items) + { + items.Enqueue(Tuple.Create(d, state)); + } + workItemsWaiting.Set(); + } + + public void EndMessageLoop() + { + Post(_ => done = true, null); + } + + public void BeginMessageLoop() + { + while (!done) + { + Tuple task = null; + lock (items) + { + if (items.Count > 0) + { + task = items.Dequeue(); + } + } + if (task != null) + { + task.Item1(task.Item2); + if (InnerException != null) // the method threw an exeption + { + throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException); + } + } + else + { + workItemsWaiting.WaitOne(); + } + } + } + + public override SynchronizationContext CreateCopy() + { + return this; + } + } + } + + // This class is taken from the Prism library by Microsoft Patterns & Practices + // License: http://compositewpf.codeplex.com/license + internal static class WeakEventHandlerManager + { + public static void AddWeakReferenceHandler(ref List handlers, EventHandler handler, int defaultListSize) + { + (handlers ??= (defaultListSize > 0) ? new List(defaultListSize) : new List()).Add(new WeakReference(handler)); + } + + private static void CallHandler(object sender, EventHandler eventHandler) + { + DispatcherProxy proxy = DispatcherProxy.CreateDispatcher(); + if (eventHandler != null) + { + if (proxy?.CheckAccess() == false) + { + proxy.BeginInvoke(new Action(CallHandler), new object[] { sender, eventHandler }); + } + else + { + eventHandler(sender, EventArgs.Empty); + } + } + } + + public static void CallWeakReferenceHandlers(object sender, List handlers) + { + if (handlers != null) + { + EventHandler[] callees = new EventHandler[handlers.Count]; + int count = 0; + count = CleanupOldHandlers(handlers, callees, count); + for (int i = 0; i < count; i++) + { + CallHandler(sender, callees[i]); + } + } + } + + private static int CleanupOldHandlers(List handlers, EventHandler[] callees, int count) + { + for (int i = handlers.Count - 1; i >= 0; i--) + { + WeakReference reference = handlers[i]; + if (reference.Target is not EventHandler target) + { + handlers.RemoveAt(i); + } + else + { + callees[count] = target; + count++; + } + } + return count; + } + + public static void RemoveWeakReferenceHandler(List handlers, EventHandler handler) + { + if (handlers != null) + { + for (int i = handlers.Count - 1; i >= 0; i--) + { + WeakReference reference = handlers[i]; + if ((reference.Target is not EventHandler target) || (target == handler)) + { + handlers.RemoveAt(i); + } + } + } + } + + private class DispatcherProxy + { + private readonly Dispatcher innerDispatcher; + + private DispatcherProxy(Dispatcher dispatcher) + { + this.innerDispatcher = dispatcher; + } + + public DispatcherOperation BeginInvoke(Delegate method, params object[] args) + { + return this.innerDispatcher.BeginInvoke(method, DispatcherPriority.Normal, args); + } + + public bool CheckAccess() + { + return this.innerDispatcher.CheckAccess(); + } + + public static DispatcherProxy CreateDispatcher() + { + if (Application.Current == null) + { + return null; + } + return new DispatcherProxy(Application.Current.Dispatcher); + } + } + } + + // This interface is taken from the Prism library by Microsoft Patterns & Practices + // License: http://compositewpf.codeplex.com/license + public interface IActiveAware + { + /// + /// Gets or sets a value indicating whether the object is active. + /// + /// if the object is active; otherwise . + bool IsActive { get; set; } + + /// + /// Notifies that the value for property has changed. + /// + event EventHandler IsActiveChanged; + } + + // This class is taken from the Prism library by Microsoft Patterns & Practices + // License: http://compositewpf.codeplex.com/license + public abstract class DelegateCommandBase : ICommand, IActiveAware + { + private List _canExecuteChangedHandlers; + private bool _isActive; + private readonly Func canExecuteMethod; + private readonly Action executeMethod; + + public event EventHandler CanExecuteChanged + { + add + { + WeakEventHandlerManager.AddWeakReferenceHandler(ref this._canExecuteChangedHandlers, value, 2); + } + remove + { + WeakEventHandlerManager.RemoveWeakReferenceHandler(this._canExecuteChangedHandlers, value); + } + } + + public event EventHandler IsActiveChanged; + + protected DelegateCommandBase(Action executeMethod, Func canExecuteMethod) + { + if ((executeMethod == null) || (canExecuteMethod == null)) + { + throw new ArgumentNullException(nameof(executeMethod), "Delegate Command Delegates Cannot Be Null"); + } + this.executeMethod = executeMethod; + this.canExecuteMethod = canExecuteMethod; + } + + protected bool CanExecute(object parameter) + { + if (this.canExecuteMethod != null) + { + return this.canExecuteMethod(parameter); + } + return true; + } + + protected void Execute(object parameter) + { + this.executeMethod(parameter); + } + + protected virtual void OnCanExecuteChanged() + { + WeakEventHandlerManager.CallWeakReferenceHandlers(this, this._canExecuteChangedHandlers); + } + + protected virtual void OnIsActiveChanged() + { + this.IsActiveChanged?.Invoke(this, EventArgs.Empty); + } + + public void RaiseCanExecuteChanged() + { + this.OnCanExecuteChanged(); + } + + bool ICommand.CanExecute(object parameter) + { + return this.CanExecute(parameter); + } + + void ICommand.Execute(object parameter) + { + this.Execute(parameter); + } + + public bool IsActive + { + get + { + return this._isActive; + } + set + { + if (this._isActive != value) + { + this._isActive = value; + this.OnIsActiveChanged(); + } + } + } + } + + // This class is taken from the Prism library by Microsoft Patterns & Practices + // License: http://compositewpf.codeplex.com/license + public class DelegateCommand : DelegateCommandBase + { + public DelegateCommand(Action executeMethod) + : this(executeMethod, () => true) + { + } + + public DelegateCommand(Action executeMethod, Func canExecuteMethod) : base(o => executeMethod(), f => canExecuteMethod()) + { + } + + public bool CanExecute() + { + return CanExecute(null); + } + + public void Execute() + { + Execute(null); + } + } + + internal class FlowDocumentScrollViewerNoMouseWheel : FlowDocumentScrollViewer + { + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + } + } + + internal class ProgressUpdater + { + private readonly DateTime InitTime; + private DateTime LastUpdateTime; + private readonly UInt64 MaxValue; + private readonly Action ProgressUpdateCallback; + internal int ProgressPercentage; + + internal ProgressUpdater(UInt64 MaxValue, Action ProgressUpdateCallback) + { + InitTime = DateTime.Now; + LastUpdateTime = DateTime.Now; + this.MaxValue = MaxValue; + this.ProgressUpdateCallback = ProgressUpdateCallback; + SetProgress(0); + } + + private UInt64 _Progress; + internal UInt64 Progress + { + get + { + return _Progress; + } + } + + internal void SetProgress(UInt64 NewValue) + { + if (_Progress != NewValue) + { + int PreviousProgressPercentage = (int)((double)_Progress / MaxValue * 100); + ProgressPercentage = (int)((double)NewValue / MaxValue * 100); + + _Progress = NewValue; + + if (((DateTime.Now - LastUpdateTime) > TimeSpan.FromSeconds(0.5)) || (ProgressPercentage == 100)) + { +#if DEBUG + Console.WriteLine("Init time: " + InitTime.ToShortTimeString() + " / Now: " + DateTime.Now.ToString() + " / NewValue: " + NewValue.ToString() + " / MaxValue: " + MaxValue.ToString() + " ->> Percentage: " + ProgressPercentage.ToString() + " / Remaining: " + TimeSpan.FromTicks((long)((DateTime.Now - InitTime).Ticks / ((double)NewValue / MaxValue) * (1 - ((double)NewValue / MaxValue)))).ToString()); +#endif + + if (((DateTime.Now - InitTime) < TimeSpan.FromSeconds(30)) && (ProgressPercentage < 15)) + { + ProgressUpdateCallback(ProgressPercentage, null); + } + else + { + ProgressUpdateCallback(ProgressPercentage, TimeSpan.FromTicks((long)((DateTime.Now - InitTime).Ticks / ((double)NewValue / MaxValue) * (1 - ((double)NewValue / MaxValue))))); + } + + LastUpdateTime = DateTime.Now; + } + } + } + + internal void IncreaseProgress(UInt64 Progress) + { + SetProgress(_Progress + Progress); + } + } + + internal static class Compression + { + internal static Stream GetDecompressedStreamWithSeek(Stream InputStream) + { + long P = InputStream.Position; + byte[] GZipHeader = new byte[3]; + InputStream.Read(GZipHeader, 0, 3); + InputStream.Position = P; + if (StructuralComparisons.StructuralEqualityComparer.Equals(GZipHeader, new byte[] { 0x1F, 0x8B, 0x08 })) + { + return new GZipStream(InputStream, CompressionMode.Decompress, false); + } + else + { + return InputStream; + } + } + + internal static bool IsCompressedStream(Stream InputStream) + { + byte[] GZipHeader = new byte[3]; + InputStream.Read(GZipHeader, 0, 3); + return StructuralComparisons.StructuralEqualityComparer.Equals(GZipHeader, new byte[] { 0x1F, 0x8B, 0x08 }); + } + + internal static GZipStream GetDecompressedStream(Stream InputStream) + { + return new GZipStream(InputStream, CompressionMode.Decompress, false); + } + } + + internal class WPinternalsException : Exception + { + // Message and SubMessaage are always printable + internal string SubMessage = null; + + internal WPinternalsException() : base() { } + + internal WPinternalsException(string Message) : base(Message) { } + + internal WPinternalsException(string Message, Exception InnerException) : base(Message, InnerException) { } + + internal WPinternalsException(string Message, string SubMessage) : base(Message) { this.SubMessage = SubMessage; } + + internal WPinternalsException(string Message, string SubMessage, Exception InnerException) : base(Message, InnerException) { this.SubMessage = SubMessage; } + } + + // This class is written by: Eugene Beresovsky + // https://stackoverflow.com/questions/13035925/stream-wrapper-to-make-stream-seekable/28036366#28036366 + internal class ReadSeekableStream : Stream + { + private long _underlyingPosition; + private readonly byte[] _seekBackBuffer; + private int _seekBackBufferCount; + private int _seekBackBufferIndex; + private readonly Stream _underlyingStream; + + public ReadSeekableStream(Stream underlyingStream, int seekBackBufferSize) + { + if (!underlyingStream.CanRead) + { + throw new Exception("Provided stream " + underlyingStream + " is not readable"); + } + + _underlyingStream = underlyingStream; + _seekBackBuffer = new byte[seekBackBufferSize]; + } + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return true; } } + + public override int Read(byte[] buffer, int offset, int count) + { + int copiedFromBackBufferCount = 0; + if (_seekBackBufferIndex < _seekBackBufferCount) + { + copiedFromBackBufferCount = Math.Min(count, _seekBackBufferCount - _seekBackBufferIndex); + Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferIndex, buffer, offset, copiedFromBackBufferCount); + offset += copiedFromBackBufferCount; + count -= copiedFromBackBufferCount; + _seekBackBufferIndex += copiedFromBackBufferCount; + } + int bytesReadFromUnderlying = 0; + if (count > 0) + { + bytesReadFromUnderlying = _underlyingStream.Read(buffer, offset, count); + if (bytesReadFromUnderlying > 0) + { + _underlyingPosition += bytesReadFromUnderlying; + + var copyToBufferCount = Math.Min(bytesReadFromUnderlying, _seekBackBuffer.Length); + var copyToBufferOffset = Math.Min(_seekBackBufferCount, _seekBackBuffer.Length - copyToBufferCount); + var bufferBytesToMove = Math.Min(_seekBackBufferCount - 1, copyToBufferOffset); + + if (bufferBytesToMove > 0) + { + Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferCount - bufferBytesToMove, _seekBackBuffer, 0, bufferBytesToMove); + } + + Buffer.BlockCopy(buffer, offset, _seekBackBuffer, copyToBufferOffset, copyToBufferCount); + _seekBackBufferCount = Math.Min(_seekBackBuffer.Length, _seekBackBufferCount + copyToBufferCount); + _seekBackBufferIndex = _seekBackBufferCount; + } + } + return copiedFromBackBufferCount + bytesReadFromUnderlying; + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (origin == SeekOrigin.End) + { + return SeekFromEnd((int)Math.Max(0, -offset)); + } + + var relativeOffset = origin == SeekOrigin.Current + ? offset + : offset - Position; + + if (relativeOffset == 0) + { + return Position; + } + else if (relativeOffset > 0) + { + return SeekForward(relativeOffset); + } + else + { + return SeekBackwards(-relativeOffset); + } + } + + private long SeekForward(long origOffset) + { + long offset = origOffset; + var seekBackBufferLength = _seekBackBuffer.Length; + + int backwardSoughtBytes = _seekBackBufferCount - _seekBackBufferIndex; + int seekForwardInBackBuffer = (int)Math.Min(offset, backwardSoughtBytes); + offset -= seekForwardInBackBuffer; + _seekBackBufferIndex += seekForwardInBackBuffer; + + if (offset > 0) + { + // first completely fill seekBackBuffer to remove special cases from while loop below + if (_seekBackBufferCount < seekBackBufferLength) + { + var maxRead = seekBackBufferLength - _seekBackBufferCount; + if (offset < maxRead) + { + maxRead = (int)offset; + } + + var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead); + _underlyingPosition += bytesRead; + _seekBackBufferCount += bytesRead; + _seekBackBufferIndex = _seekBackBufferCount; + if (bytesRead < maxRead) + { + if (_seekBackBufferCount < offset) + { + throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes"); + } + + return Position; + } + offset -= bytesRead; + } + + // now alternate between filling tempBuffer and seekBackBuffer + bool fillTempBuffer = true; + var tempBuffer = new byte[seekBackBufferLength]; + while (offset > 0) + { + var maxRead = offset < seekBackBufferLength ? (int)offset : seekBackBufferLength; + var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, maxRead); + _underlyingPosition += bytesRead; + var bytesReadDiff = maxRead - bytesRead; + offset -= bytesRead; + if (bytesReadDiff > 0 /* reached end-of-stream */ || offset == 0) + { + if (fillTempBuffer) + { + if (bytesRead > 0) + { + Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); + Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); + } + } + else + { + if (bytesRead > 0) + { + Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); + } + + Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); + } + if (offset > 0) + { + throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes"); + } + } + fillTempBuffer = !fillTempBuffer; + } + } + return Position; + } + + private long SeekBackwards(long offset) + { + var intOffset = (int)offset; + if (offset > int.MaxValue || intOffset > _seekBackBufferIndex) + { + throw new NotSupportedException("Cannot currently seek backwards more than " + _seekBackBufferIndex + " bytes"); + } + + _seekBackBufferIndex -= intOffset; + return Position; + } + + private long SeekFromEnd(long offset) + { + var intOffset = (int)offset; + var seekBackBufferLength = _seekBackBuffer.Length; + if (offset > int.MaxValue || intOffset > seekBackBufferLength) + { + throw new NotSupportedException("Cannot seek backwards from end more than " + seekBackBufferLength + " bytes"); + } + + // first completely fill seekBackBuffer to remove special cases from while loop below + if (_seekBackBufferCount < seekBackBufferLength) + { + var maxRead = seekBackBufferLength - _seekBackBufferCount; + var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead); + _underlyingPosition += bytesRead; + _seekBackBufferCount += bytesRead; + _seekBackBufferIndex = Math.Max(0, _seekBackBufferCount - intOffset); + if (bytesRead < maxRead) + { + if (_seekBackBufferCount < intOffset) + { + throw new NotSupportedException("Could not seek backwards from end " + intOffset + " bytes"); + } + + return Position; + } + } + else + { + _seekBackBufferIndex = _seekBackBufferCount; + } + + // now alternate between filling tempBuffer and seekBackBuffer + bool fillTempBuffer = true; + var tempBuffer = new byte[seekBackBufferLength]; + while (true) + { + var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, seekBackBufferLength); + _underlyingPosition += bytesRead; + var bytesReadDiff = seekBackBufferLength - bytesRead; + if (bytesReadDiff > 0) // reached end-of-stream + { + if (fillTempBuffer) + { + if (bytesRead > 0) + { + Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); + Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); + } + } + else + { + if (bytesRead > 0) + { + Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); + } + + Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); + } + _seekBackBufferIndex -= intOffset; + return Position; + } + fillTempBuffer = !fillTempBuffer; + } + } + + public override long Position + { + get { return _underlyingPosition - (_seekBackBufferCount - _seekBackBufferIndex); } + set { Seek(value, SeekOrigin.Begin); } + } + + public override bool CanTimeout { get { return _underlyingStream.CanTimeout; } } + public override bool CanWrite { get { return _underlyingStream.CanWrite; } } + public override long Length { get { return _underlyingStream.Length; } } + public override void SetLength(long value) { _underlyingStream.SetLength(value); } + public override void Write(byte[] buffer, int offset, int count) { _underlyingStream.Write(buffer, offset, count); } + public override void Flush() { _underlyingStream.Flush(); } + public override void Close() { _underlyingStream.Close(); } + public new void Dispose() { _underlyingStream.Dispose(); } + protected override void Dispose(bool disposing) + { + if (disposing) + { + _underlyingStream.Dispose(); + } + } + } + + // For reading a compressed stream or normal stream + internal class DecompressedStream : Stream + { + private readonly Stream UnderlyingStream; + private readonly bool IsSourceCompressed; + private readonly UInt64 DecompressedLength; + private Int64 ReadPosition = 0; + + // For reading a compressed stream + internal DecompressedStream(Stream InputStream) + { + UnderlyingStream = new ReadSeekableStream(InputStream, 0x100); + + byte[] Signature = new byte["CompressedPartition".Length + 2]; + Signature[0x00] = 0xFF; + Buffer.BlockCopy(Encoding.ASCII.GetBytes("CompressedPartition"), 0, Signature, 0x01, "CompressedPartition".Length); + Signature["CompressedPartition".Length + 1] = 0x00; + + int PrimaryHeaderSize = 0x0A + "CompressedPartition".Length; + byte[] SignatureRead = new byte[Signature.Length]; + UnderlyingStream.Read(SignatureRead, 0, Signature.Length); + + IsSourceCompressed = StructuralComparisons.StructuralEqualityComparer.Equals(Signature, SignatureRead); + if (IsSourceCompressed) + { + byte[] FormatVersionBytes = new byte[4]; + UnderlyingStream.Read(FormatVersionBytes, 0, 4); + if (BitConverter.ToUInt32(FormatVersionBytes, 0) > 1) // Max supported format version = 1 + { + throw new InvalidDataException(); + } + + byte[] HeaderSizeBytes = new byte[4]; + UnderlyingStream.Read(HeaderSizeBytes, 0, 4); + UInt32 HeaderSize = BitConverter.ToUInt32(HeaderSizeBytes, 0); + + if (HeaderSize >= (Signature.Length + 0x10)) + { + byte[] DecompressedLengthBytes = new byte[8]; + UnderlyingStream.Read(DecompressedLengthBytes, 0, 8); + DecompressedLength = BitConverter.ToUInt64(DecompressedLengthBytes, 0); + } + else + { + throw new InvalidDataException(); + } + + UInt32 HeaderBytesRemaining = (UInt32)(HeaderSize - Signature.Length - 0x10); + if (HeaderBytesRemaining > 0) + { + byte[] HeaderBytes = new byte[HeaderBytesRemaining]; + UnderlyingStream.Read(HeaderBytes, 0, (int)HeaderBytesRemaining); + } + + UnderlyingStream = new GZipStream(UnderlyingStream, CompressionMode.Decompress, false); + } + else + { + UnderlyingStream.Position = 0; + } + } + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override int Read(byte[] buffer, int offset, int count) + { + int RealCount = UnderlyingStream.Read(buffer, offset, count); + ReadPosition += RealCount; + return RealCount; + } + public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public override long Position + { + get { return ReadPosition; } + set { throw new NotSupportedException(); } + } + public override bool CanTimeout { get { return UnderlyingStream.CanTimeout; } } + public override bool CanWrite { get { return true; } } + public override long Length + { + get + { + if (IsSourceCompressed) + { + return (long)DecompressedLength; + } + else + { + return UnderlyingStream.Length; + } + } + } + public override void SetLength(long value) { throw new NotSupportedException(); } + public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public override void Flush() { UnderlyingStream.Flush(); } + public override void Close() { UnderlyingStream.Close(); } + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.UnderlyingStream.Dispose(); + } + } + } + + // For writing a compressed stream + internal class CompressedStream : Stream + { + private readonly UInt32 HeaderSize; + private UInt64 WritePosition; + private readonly GZipStream UnderlyingStream; + + internal CompressedStream(Stream OutputStream, UInt64 TotalDecompressedStreamLength) + { + // Write header + HeaderSize = (UInt32)(0x12 + "CompressedPartition".Length); + OutputStream.WriteByte(0xFF); + OutputStream.Write(Encoding.ASCII.GetBytes("CompressedPartition"), 0, "CompressedPartition".Length); + OutputStream.WriteByte(0x00); + OutputStream.Write(BitConverter.GetBytes((UInt32)1), 0, 4); // Format version = 1 + OutputStream.Write(BitConverter.GetBytes(HeaderSize), 0, 4); // Headersize + OutputStream.Write(BitConverter.GetBytes(TotalDecompressedStreamLength), 0, 8); + + this.UnderlyingStream = new GZipStream(OutputStream, CompressionLevel.Optimal, false); + WritePosition = 0; + } + + public override bool CanRead { get { return false; } } + public override bool CanSeek { get { return false; } } + public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public override long Position + { + get { return (long)WritePosition; } + set { throw new NotSupportedException(); } + } + public override bool CanTimeout { get { return UnderlyingStream.CanTimeout; } } + public override bool CanWrite { get { return true; } } + public override long Length { get { return (long)WritePosition; } } + public override void SetLength(long value) { throw new NotSupportedException(); } + public override void Write(byte[] buffer, int offset, int count) + { + WritePosition += (UInt64)count; + UnderlyingStream.Write(buffer, offset, count); + } + public override void Flush() { UnderlyingStream.Flush(); } + public override void Close() { UnderlyingStream.Close(); } + public new void Dispose() { UnderlyingStream.Dispose(); } + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.UnderlyingStream.Dispose(); + } + } + } + + internal class SeekableStream : Stream + { + private Stream UnderlyingStream; + private Int64 ReadPosition = 0; + private readonly Func StreamInitializer; + private readonly Int64 UnderlyingStreamLength; + + // For reading a compressed stream + internal SeekableStream(Func StreamInitializer, Int64? Length = null) + { + this.StreamInitializer = StreamInitializer; + UnderlyingStream = StreamInitializer(); + if (Length != null) + { + UnderlyingStreamLength = (Int64)Length; + } + else + { + try + { + UnderlyingStreamLength = UnderlyingStream.Length; + } + catch + { + throw new ArgumentException("Unknown stream length"); + } + } + } + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return true; } } + public override int Read(byte[] buffer, int offset, int count) + { + int RealCount = UnderlyingStream.Read(buffer, offset, count); + ReadPosition += RealCount; + return RealCount; + } + public override long Seek(long offset, SeekOrigin origin) + { + if (UnderlyingStream.CanSeek) + { + ReadPosition = UnderlyingStream.Seek(offset, origin); + return ReadPosition; + } + else + { + Int64 NewPosition = 0; + switch (origin) + { + case SeekOrigin.Begin: + NewPosition = offset; + break; + case SeekOrigin.Current: + NewPosition = ReadPosition + offset; + break; + case SeekOrigin.End: + NewPosition = UnderlyingStreamLength - offset; + break; + } + if ((NewPosition < 0) || (NewPosition > UnderlyingStreamLength)) + { + throw new ArgumentOutOfRangeException(); + } + + if (NewPosition < ReadPosition) + { + UnderlyingStream.Close(); + UnderlyingStream = StreamInitializer(); + ReadPosition = 0; + } + UInt64 Remaining; + byte[] Buffer = new byte[16384]; + while (ReadPosition < NewPosition) + { + Remaining = (UInt64)(NewPosition - ReadPosition); + if (Remaining > (UInt64)Buffer.Length) + { + Remaining = (UInt64)Buffer.Length; + } + + UnderlyingStream.Read(Buffer, 0, (int)Remaining); + ReadPosition += (long)Remaining; + } + return ReadPosition; + } + } + public override long Position + { + get + { + return ReadPosition; + } + set + { + Seek(value, SeekOrigin.Begin); + } + } + public override bool CanTimeout { get { return UnderlyingStream.CanTimeout; } } + public override bool CanWrite { get { return false; } } + public override long Length + { + get + { + return UnderlyingStreamLength; + } + } + public override void SetLength(long value) { throw new NotSupportedException(); } + public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public override void Flush() { throw new NotSupportedException(); } + public override void Close() { UnderlyingStream.Close(); } + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.UnderlyingStream.Dispose(); + } + } + } + + internal enum ResourceType + { + RT_CURSOR = 1, + RT_BITMAP = 2, + RT_ICON = 3, + RT_MENU = 4, + RT_DIALOG = 5, + RT_STRING = 6, + RT_FONTDIR = 7, + RT_FONT = 8, + RT_ACCELERATOR = 9, + RT_RCDATA = 10, + RT_MESSAGETABLE = 11, + RT_GROUP_CURSOR = RT_CURSOR + 11, + RT_GROUP_ICON = RT_ICON + 11, + RT_VERSION = 16, + RT_DLGINCLUDE = 17, + RT_PLUGPLAY = 19, + RT_VXD = 20, + RT_ANICURSOR = 21, + RT_ANIICON = 22, + RT_HTML = 23, + RT_MANIFEST = 24, + RT_DLGINIT = 240, + RT_TOOLBAR = 241 + }; + + internal static class PE + { + internal static byte[] GetResource(byte[] PEfile, int[] Index) + { + // Explanation of PE header here: + // https://msdn.microsoft.com/en-us/library/ms809762.aspx?f=255&MSPPError=-2147217396 + + UInt32 PEPointer = ByteOperations.ReadUInt32(PEfile, 0x3C); + UInt16 OptionalHeaderSize = ByteOperations.ReadUInt16(PEfile, PEPointer + 0x14); + UInt32 SectionTablePointer = PEPointer + 0x18 + OptionalHeaderSize; + UInt16 SectionCount = ByteOperations.ReadUInt16(PEfile, PEPointer + 0x06); + UInt32? ResourceSectionEntryPointer = null; + for (int i = 0; i < SectionCount; i++) + { + string SectionName = ByteOperations.ReadAsciiString(PEfile, (UInt32)(SectionTablePointer + (i * 0x28)), 8); + int e = SectionName.IndexOf('\0'); + if (e >= 0) + { + SectionName = SectionName.Substring(0, e); + } + + if (SectionName == ".rsrc") + { + ResourceSectionEntryPointer = (UInt32)(SectionTablePointer + (i * 0x28)); + break; + } + } + if (ResourceSectionEntryPointer == null) + { + throw new WPinternalsException("Resource-section not found"); + } + + UInt32 ResourceRawSize = ByteOperations.ReadUInt32(PEfile, (UInt32)ResourceSectionEntryPointer + 0x10); + UInt32 ResourceRawPointer = ByteOperations.ReadUInt32(PEfile, (UInt32)ResourceSectionEntryPointer + 0x14); + UInt32 ResourceVirtualPointer = ByteOperations.ReadUInt32(PEfile, (UInt32)ResourceSectionEntryPointer + 0x0C); + + UInt32 p = ResourceRawPointer; + for (int i = 0; i < Index.Length; i++) + { + UInt16 ResourceNamedEntryCount = ByteOperations.ReadUInt16(PEfile, p + 0x0c); + UInt16 ResourceIdEntryCount = ByteOperations.ReadUInt16(PEfile, p + 0x0e); + for (int j = ResourceNamedEntryCount; j < ResourceNamedEntryCount + ResourceIdEntryCount; j++) + { + UInt32 ResourceID = ByteOperations.ReadUInt32(PEfile, (UInt32)(p + 0x10 + (j * 8))); + UInt32 NextPointer = ByteOperations.ReadUInt32(PEfile, (UInt32)(p + 0x10 + (j * 8) + 4)); + if (ResourceID == (UInt32)Index[i]) + { + // Check high bit + if ((NextPointer & 0x80000000) == 0 != (i == (Index.Length - 1))) + { + throw new WPinternalsException("Bad resource path"); + } + + p = ResourceRawPointer + (NextPointer & 0x7fffffff); + break; + } + } + } + + UInt32 ResourceValuePointer = ByteOperations.ReadUInt32(PEfile, p) - ResourceVirtualPointer + ResourceRawPointer; + UInt32 ResourceValueSize = ByteOperations.ReadUInt32(PEfile, p + 4); + + byte[] ResourceValue = new byte[ResourceValueSize]; + Array.Copy(PEfile, ResourceValuePointer, ResourceValue, 0, ResourceValueSize); + + return ResourceValue; + } + + internal static Version GetFileVersion(byte[] PEfile) + { + byte[] version = GetResource(PEfile, new int[] { (int)ResourceType.RT_VERSION, 1, 1033 }); + + // RT_VERSION format: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms646997(v=vs.85).aspx + const UInt32 FixedFileInfoPointer = 0x28; + UInt16 Major = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0A); + UInt16 Minor = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x08); + UInt16 Build = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0E); + UInt16 Revision = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0C); + + return new Version(Major, Minor, Build, Revision); + } + + internal static Version GetProductVersion(byte[] PEfile) + { + byte[] version = GetResource(PEfile, new int[] { (int)ResourceType.RT_VERSION, 1, 1033 }); + + // RT_VERSION format: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms646997(v=vs.85).aspx + const UInt32 FixedFileInfoPointer = 0x28; + UInt16 Major = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x12); + UInt16 Minor = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x10); + UInt16 Build = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x16); + UInt16 Revision = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x14); + + return new Version(Major, Minor, Build, Revision); + } + } + +#if PREVIEW + internal static class Uploader + { + internal static List Uploads = new List(); + + internal static void Upload(string FileName, string Text) + { + byte[] byteArray = Encoding.UTF8.GetBytes(Text); + MemoryStream FileStream = new MemoryStream(byteArray); + Upload(FileName, FileStream); + } + + internal static void Upload(string FileName, byte[] Data) + { + Upload(FileName, new MemoryStream(Data)); + } + + internal static void Upload(string FileName, Stream FileStream) + { + Upload(new Uri(@"https://www.wpinternals.net/upload.php", UriKind.Absolute), "uploadedfile", FileName, FileStream); + } + + private static void Upload(Uri Address, string InputName, string FileName, Stream FileStream) + { + System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; + System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient(); + System.Net.Http.MultipartFormDataContent form = new System.Net.Http.MultipartFormDataContent(); + System.Net.Http.StreamContent Content = new System.Net.Http.StreamContent(FileStream); + Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/plain"); + form.Add(Content, InputName, FileName); + Task UploadTask = httpClient.PostAsync(Address, form); + + Uploads.Add( + UploadTask.ContinueWith((t) => + { + Uploads.Remove(t); + httpClient.Dispose(); + }) + ); + } + + internal static void WaitForUploads() + { + Task.WaitAll(Uploads.ToArray()); + } + } +#endif + + internal class AsyncAutoResetEvent + { + private readonly LinkedList> waiters = + new(); + + private bool isSignaled; + + public AsyncAutoResetEvent(bool signaled) + { + this.isSignaled = signaled; + } + + public Task WaitAsync(TimeSpan timeout) + { + return this.WaitAsync(timeout, CancellationToken.None); + } + + public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + TaskCompletionSource tcs; + + lock (this.waiters) + { + if (this.isSignaled) + { + this.isSignaled = false; + return true; + } + else if (timeout == TimeSpan.Zero) + { + return this.isSignaled; + } + else + { + tcs = new TaskCompletionSource(); + this.waiters.AddLast(tcs); + } + } + + Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)); + if (winner == tcs.Task) + { + // The task was signaled. + return true; + } + else + { + // We timed-out; remove our reference to the task. + // This is an O(n) operation since waiters is a LinkedList. + lock (this.waiters) + { + bool removed = this.waiters.Remove(tcs); + System.Diagnostics.Debug.Assert(removed); + return false; + } + } + } + + public void Set() + { + TaskCompletionSource toRelease = null; + + lock (this.waiters) + { + if (this.waiters.Count > 0) + { + // Signal the first task in the waiters list. + toRelease = this.waiters.First.Value; + this.waiters.RemoveFirst(); + } + else if (!this.isSignaled) + { + // No tasks are pending + this.isSignaled = true; + } + } + + toRelease?.SetResult(true); + } + } + + // This class was written by: Rolf Wessels + // https://github.com/rolfwessels/lazycowprojects/tree/master/Wpf + + /// + /// Static class used to attach to wpf control + /// + public static class GridViewColumnResize + { + #region DependencyProperties + + public static readonly DependencyProperty WidthProperty = + DependencyProperty.RegisterAttached("Width", typeof(string), typeof(GridViewColumnResize), + new PropertyMetadata(OnSetWidthCallback)); + + public static readonly DependencyProperty GridViewColumnResizeBehaviorProperty = + DependencyProperty.RegisterAttached("GridViewColumnResizeBehavior", + typeof(GridViewColumnResizeBehavior), typeof(GridViewColumnResize), + null); + + public static readonly DependencyProperty EnabledProperty = + DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(GridViewColumnResize), + new PropertyMetadata(OnSetEnabledCallback)); + + public static readonly DependencyProperty ListViewResizeBehaviorProperty = + DependencyProperty.RegisterAttached("ListViewResizeBehaviorProperty", + typeof(ListViewResizeBehavior), typeof(GridViewColumnResize), null); + + #endregion + + public static string GetWidth(DependencyObject obj) + { + return (string)obj.GetValue(WidthProperty); + } + + public static void SetWidth(DependencyObject obj, string value) + { + obj.SetValue(WidthProperty, value); + } + + public static bool GetEnabled(DependencyObject obj) + { + return (bool)obj.GetValue(EnabledProperty); + } + + public static void SetEnabled(DependencyObject obj, bool value) + { + obj.SetValue(EnabledProperty, value); + } + + #region CallBack + + private static void OnSetWidthCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + if (dependencyObject is GridViewColumn element) + { + GridViewColumnResizeBehavior behavior = GetOrCreateBehavior(element); + behavior.Width = e.NewValue as string; + } + else + { + Console.Error.WriteLine("Error: Expected type GridViewColumn but found " + + dependencyObject.GetType().Name); + } + } + + private static void OnSetEnabledCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + if (dependencyObject is ListView element) + { + ListViewResizeBehavior behavior = GetOrCreateBehavior(element); + behavior.Enabled = (bool)e.NewValue; + } + else + { + Console.Error.WriteLine("Error: Expected type ListView but found " + dependencyObject.GetType().Name); + } + } + + private static ListViewResizeBehavior GetOrCreateBehavior(ListView element) + { + if (element.GetValue(GridViewColumnResizeBehaviorProperty) is not ListViewResizeBehavior behavior) + { + behavior = new ListViewResizeBehavior(element); + element.SetValue(ListViewResizeBehaviorProperty, behavior); + } + + return behavior; + } + + private static GridViewColumnResizeBehavior GetOrCreateBehavior(GridViewColumn element) + { + if (element.GetValue(GridViewColumnResizeBehaviorProperty) is not GridViewColumnResizeBehavior behavior) + { + behavior = new GridViewColumnResizeBehavior(element); + element.SetValue(GridViewColumnResizeBehaviorProperty, behavior); + } + + return behavior; + } + + #endregion + + #region Nested type: GridViewColumnResizeBehavior + + // This class was written by: Rolf Wessels + // https://github.com/rolfwessels/lazycowprojects/tree/master/Wpf + + /// + /// GridViewColumn class that gets attached to the GridViewColumn control + /// + public class GridViewColumnResizeBehavior + { + private readonly GridViewColumn _element; + + public GridViewColumnResizeBehavior(GridViewColumn element) + { + _element = element; + } + + public string Width { get; set; } + + public bool IsStatic + { + get { return StaticWidth >= 0; } + } + + public double StaticWidth + { + get + { + return double.TryParse(Width, out double result) ? result : -1; + } + } + + public double Percentage + { + get + { + if (!IsStatic) + { + return Mulitplier * 100; + } + return 0; + } + } + + public double Mulitplier + { + get + { + if (Width == "*" || Width == "1*") + { + return 1; + } + + if (Width.EndsWith("*") && double.TryParse(Width[0..^1], out double perc)) + { + return perc; + } + return 1; + } + } + + public void SetWidth(double allowedSpace, double totalPercentage) + { + if (IsStatic) + { + _element.Width = StaticWidth; + } + else + { + _element.Width = (double)Math.Max(allowedSpace * (Percentage / totalPercentage), 0); + } + } + } + + #endregion + + #region Nested type: ListViewResizeBehavior + + // This class was written by: Rolf Wessels + // https://github.com/rolfwessels/lazycowprojects/tree/master/Wpf + + /// + /// ListViewResizeBehavior class that gets attached to the ListView control + /// + public class ListViewResizeBehavior + { + private const int Margin = 25; + private const long RefreshTime = Timeout.Infinite; + private const long Delay = 500; + + private readonly ListView _element; + private readonly Timer _timer; + + public ListViewResizeBehavior(ListView element) + { + _element = element ?? throw new ArgumentNullException(nameof(element)); + element.Loaded += OnLoaded; + + // Action for resizing and re-enable the size lookup + // This stops the columns from constantly resizing to improve performance + Action resizeAndEnableSize = () => + { + Resize(); + _element.SizeChanged += OnSizeChanged; + }; + _timer = new Timer(x => Application.Current.Dispatcher.BeginInvoke(resizeAndEnableSize), null, Delay, + RefreshTime); + } + + public bool Enabled { get; set; } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + _element.SizeChanged += OnSizeChanged; + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + if (e.WidthChanged) + { + _element.SizeChanged -= OnSizeChanged; + _timer.Change(Delay, RefreshTime); + } + } + + private void Resize() + { + if (Enabled) + { + double totalWidth = _element.ActualWidth; + if (_element.View is GridView gv) + { + double allowedSpace = totalWidth - GetAllocatedSpace(gv); + allowedSpace -= Margin; + double totalPercentage = GridViewColumnResizeBehaviors(gv).Sum(x => x.Percentage); + foreach (GridViewColumnResizeBehavior behavior in GridViewColumnResizeBehaviors(gv)) + { + behavior.SetWidth(allowedSpace, totalPercentage); + } + } + } + } + + private static IEnumerable GridViewColumnResizeBehaviors(GridView gv) + { + foreach (GridViewColumn t in gv.Columns) + { + if (t.GetValue(GridViewColumnResizeBehaviorProperty) is GridViewColumnResizeBehavior gridViewColumnResizeBehavior) + { + yield return gridViewColumnResizeBehavior; + } + } + } + + private static double GetAllocatedSpace(GridView gv) + { + double totalWidth = 0; + foreach (GridViewColumn t in gv.Columns) + { + if (t.GetValue(GridViewColumnResizeBehaviorProperty) is GridViewColumnResizeBehavior gridViewColumnResizeBehavior) + { + if (gridViewColumnResizeBehavior.IsStatic) + { + totalWidth += gridViewColumnResizeBehavior.StaticWidth; + } + } + else + { + totalWidth += t.ActualWidth; + } + } + return totalWidth; + } + } + + #endregion + } + + internal static class ExtensionMethods + { + // This method was written by: Lawrence Johnston + // https://stackoverflow.com/a/22078975 + public static async Task TimeoutAfter(this Task task, TimeSpan timeout) + { + using var timeoutCancellationTokenSource = new CancellationTokenSource(); + var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)); + if (completedTask == task) + { + timeoutCancellationTokenSource.Cancel(); + return await task; // Very important in order to propagate exceptions + } + else + { + throw new TimeoutException("The operation has timed out."); + } + } + } +} diff --git a/Logo-Small.png b/WPinternals/Logo-Small.png similarity index 100% rename from Logo-Small.png rename to WPinternals/Logo-Small.png diff --git a/Logo.png b/WPinternals/Logo.png similarity index 100% rename from Logo.png rename to WPinternals/Logo.png diff --git a/Models/ByteOperations.cs b/WPinternals/Models/ByteOperations.cs similarity index 97% rename from Models/ByteOperations.cs rename to WPinternals/Models/ByteOperations.cs index 738fb67..a59a295 100644 --- a/Models/ByteOperations.cs +++ b/WPinternals/Models/ByteOperations.cs @@ -1,401 +1,401 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -// These functions assume same endianness for the CPU architecture and the raw data it reads from or writes to. - -using System; -using System.IO; - -namespace WPinternals -{ - internal static class ByteOperations - { - internal static string ReadAsciiString(byte[] ByteArray, UInt32 Offset, UInt32 Length) - { - byte[] Bytes = new byte[Length]; - Buffer.BlockCopy(ByteArray, (int)Offset, Bytes, 0, (int)Length); - return System.Text.Encoding.ASCII.GetString(Bytes); - } - - internal static string ReadUnicodeString(byte[] ByteArray, UInt32 Offset, UInt32 Length) - { - byte[] Bytes = new byte[Length]; - Buffer.BlockCopy(ByteArray, (int)Offset, Bytes, 0, (int)Length); - return System.Text.Encoding.Unicode.GetString(Bytes); - } - - internal static void WriteAsciiString(byte[] ByteArray, UInt32 Offset, string Text, UInt32? MaxBufferLength = null) - { - if (MaxBufferLength != null) - { - Array.Clear(ByteArray, (int)Offset, (int)MaxBufferLength); - } - - byte[] TextBytes = System.Text.Encoding.ASCII.GetBytes(Text); - int WriteLength = TextBytes.Length; - if (WriteLength > MaxBufferLength) - { - WriteLength = (int)MaxBufferLength; - } - - Buffer.BlockCopy(TextBytes, 0, ByteArray, (int)Offset, WriteLength); - } - - internal static void WriteUnicodeString(byte[] ByteArray, UInt32 Offset, string Text, UInt32? MaxBufferLength = null) - { - if (MaxBufferLength != null) - { - Array.Clear(ByteArray, (int)Offset, (int)MaxBufferLength); - } - - byte[] TextBytes = System.Text.Encoding.Unicode.GetBytes(Text); - int WriteLength = TextBytes.Length; - if (WriteLength > MaxBufferLength) - { - WriteLength = (int)MaxBufferLength; - } - - Buffer.BlockCopy(TextBytes, 0, ByteArray, (int)Offset, WriteLength); - } - - internal static UInt32 ReadUInt32(byte[] ByteArray, UInt32 Offset) - { - return BitConverter.ToUInt32(ByteArray, (int)Offset); - } - - internal static void WriteUInt32(byte[] ByteArray, UInt32 Offset, UInt32 Value) - { - Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 4); - } - - internal static Int32 ReadInt32(byte[] ByteArray, UInt32 Offset) - { - return BitConverter.ToInt32(ByteArray, (int)Offset); - } - - internal static void WriteInt32(byte[] ByteArray, UInt32 Offset, Int32 Value) - { - Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 4); - } - - internal static UInt16 ReadUInt16(byte[] ByteArray, UInt32 Offset) - { - return BitConverter.ToUInt16(ByteArray, (int)Offset); - } - - internal static void WriteUInt16(byte[] ByteArray, UInt32 Offset, UInt16 Value) - { - Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 2); - } - - internal static Int16 ReadInt16(byte[] ByteArray, UInt32 Offset) - { - return BitConverter.ToInt16(ByteArray, (int)Offset); - } - - internal static void WriteInt16(byte[] ByteArray, UInt32 Offset, Int16 Value) - { - Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 2); - } - - internal static byte ReadUInt8(byte[] ByteArray, UInt32 Offset) - { - return ByteArray[Offset]; - } - - internal static void WriteUInt8(byte[] ByteArray, UInt32 Offset, byte Value) - { - ByteArray[Offset] = Value; - } - - internal static UInt32 ReadUInt24(byte[] ByteArray, UInt32 Offset) - { - return (UInt32)(ByteArray[Offset] + (ByteArray[Offset + 1] << 8) + (ByteArray[Offset + 2] << 16)); - } - - internal static void WriteUInt24(byte[] ByteArray, UInt32 Offset, UInt32 Value) - { - Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 3); - } - - internal static UInt64 ReadUInt64(byte[] ByteArray, UInt32 Offset) - { - return BitConverter.ToUInt64(ByteArray, (int)Offset); - } - - internal static void WriteUInt64(byte[] ByteArray, UInt32 Offset, UInt64 Value) - { - Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 8); - } - - internal static Guid ReadGuid(byte[] ByteArray, UInt32 Offset) - { - byte[] GuidBuffer = new byte[0x10]; - Buffer.BlockCopy(ByteArray, (int)Offset, GuidBuffer, 0, 0x10); - return new Guid(GuidBuffer); - } - - internal static void WriteGuid(byte[] ByteArray, UInt32 Offset, Guid Value) - { - Buffer.BlockCopy(Value.ToByteArray(), 0, ByteArray, (int)Offset, 0x10); - } - - internal static UInt32 Align(UInt32 Base, UInt32 Offset, UInt32 Alignment) - { - if (((Offset - Base) % Alignment) == 0) - { - return Offset; - } - else - { - return ((((Offset - Base) / Alignment) + 1) * Alignment) + Base; - } - } - - internal static UInt32? FindPatternInFile(string FileName, byte[] Pattern, byte[] Mask, out byte[] OutPattern) - { - // The mask is optional. - // In the mask 0x00 means the value must match, and 0xFF means that this position is a wildcard. - - UInt32? Result = null; - - FileStream Stream = new(FileName, FileMode.Open, FileAccess.Read); - - byte[] Buffer = new byte[0x10000 + Pattern.Length - 1]; - UInt32 BufferReadPosition = 0; // Position in buffer where file-chunk is being read. - UInt32 BytesInBuffer = 0; - UInt32 BytesRead; - UInt32 SearchPositionFile = 0; - UInt32 SearchPositionBuffer = 0; - UInt32 BufferFileOffset = 0; // Offset in file where data from buffer is located. - bool Match = false; - int i; - - OutPattern = null; - - while (SearchPositionFile <= (Stream.Length - Pattern.Length)) - { - if ((SearchPositionBuffer + Pattern.Length) > BytesInBuffer) - { - // Need to read next chunk - if ((BytesInBuffer - SearchPositionBuffer) > 0) - { - System.Buffer.BlockCopy(Buffer, (int)SearchPositionBuffer, Buffer, 0, (int)(BytesInBuffer - SearchPositionBuffer)); - } - BufferReadPosition = BytesInBuffer - SearchPositionBuffer; - BytesInBuffer -= SearchPositionBuffer; - BufferFileOffset += SearchPositionBuffer; - SearchPositionBuffer = 0; - - BytesRead = (UInt32)Stream.Read(Buffer, (int)BufferReadPosition, Buffer.Length - (int)BufferReadPosition); - BytesInBuffer += BytesRead; - } - - Match = true; - for (i = 0; i < Pattern.Length; i++) - { - if (Buffer[SearchPositionBuffer + i] != Pattern[i]) - { - if ((Mask == null) || (Mask[i] == 0)) - { - Match = false; - break; - } - } - } - - if (Match) - { - Result = SearchPositionFile; - - OutPattern = new byte[Pattern.Length]; - System.Buffer.BlockCopy(Buffer, (int)SearchPositionBuffer, OutPattern, 0, Pattern.Length); - break; - } - - SearchPositionBuffer++; - SearchPositionFile++; - } - - Stream.Close(); - - return Result; - } - - internal static UInt32? FindAscii(byte[] SourceBuffer, string Pattern) - { - return FindPattern(SourceBuffer, System.Text.Encoding.ASCII.GetBytes(Pattern), null, null); - } - - internal static UInt32? FindUnicode(byte[] SourceBuffer, string Pattern) - { - return FindPattern(SourceBuffer, System.Text.Encoding.Unicode.GetBytes(Pattern), null, null); - } - - internal static UInt32? FindUint(byte[] SourceBuffer, UInt32 Pattern) - { - return FindPattern(SourceBuffer, BitConverter.GetBytes(Pattern), null, null); - } - - internal static UInt32? FindPattern(byte[] SourceBuffer, byte[] Pattern, byte[] Mask, byte[] OutPattern) - { - return FindPattern(SourceBuffer, 0, null, Pattern, Mask, OutPattern); - } - - internal static bool Compare(byte[] Array1, byte[] Array2) - { - return System.Collections.StructuralComparisons.StructuralEqualityComparer.Equals(Array1, Array2); - } - - internal static UInt32? FindPattern(byte[] SourceBuffer, uint SourceOffset, uint? SourceSize, byte[] Pattern, byte[] Mask, byte[] OutPattern) - { - // The mask is optional. - // In the mask 0x00 means the value must match, and 0xFF means that this position is a wildcard. - - UInt32? Result = null; - - UInt32 SearchPosition = SourceOffset; - bool Match = false; - int i; - - while ((SearchPosition <= (SourceBuffer.Length - Pattern.Length)) && ((SourceSize == null) || (SearchPosition <= (SourceOffset + SourceSize - Pattern.Length)))) - { - Match = true; - for (i = 0; i < Pattern.Length; i++) - { - if (SourceBuffer[SearchPosition + i] != Pattern[i]) - { - if ((Mask == null) || (Mask[i] == 0)) - { - Match = false; - break; - } - } - } - - if (Match) - { - Result = SearchPosition; - - if (OutPattern != null) - { - Buffer.BlockCopy(SourceBuffer, (int)SearchPosition, OutPattern, 0, Pattern.Length); - } - - break; - } - - SearchPosition++; - } - - return Result; - } - - internal static byte CalculateChecksum8(byte[] Buffer, UInt32 Offset, UInt32 Size) - { - byte Checksum = 0; - - for (UInt32 i = Offset; i < (Offset + Size); i++) - { - Checksum += Buffer[i]; - } - - return (byte)(0x100 - Checksum); - } - - internal static UInt16 CalculateChecksum16(byte[] Buffer, UInt32 Offset, UInt32 Size) - { - UInt16 Checksum = 0; - - for (UInt32 i = Offset; i < (Offset + Size - 1); i += 2) - { - Checksum += BitConverter.ToUInt16(Buffer, (int)i); - } - - return (UInt16)(0x10000 - Checksum); - } - - private static readonly UInt32[] CRC32Table = new UInt32[] { - 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 - }; - - internal static UInt32 CRC32(byte[] Input, UInt32 Offset, UInt32 Length) - { - if ((Input == null) || ((Offset + Length) > Input.Length)) - { - throw new ArgumentException(); - } - - unchecked - { - uint crc = (uint)(((uint)0) ^ (-1)); - for (var i = Offset; i < (Offset + Length); i++) - { - crc = (crc >> 8) ^ CRC32Table[(crc ^ Input[i]) & 0xFF]; - } - crc = (uint)(crc ^ (-1)); - - return crc; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// These functions assume same endianness for the CPU architecture and the raw data it reads from or writes to. + +using System; +using System.IO; + +namespace WPinternals +{ + internal static class ByteOperations + { + internal static string ReadAsciiString(byte[] ByteArray, UInt32 Offset, UInt32 Length) + { + byte[] Bytes = new byte[Length]; + Buffer.BlockCopy(ByteArray, (int)Offset, Bytes, 0, (int)Length); + return System.Text.Encoding.ASCII.GetString(Bytes); + } + + internal static string ReadUnicodeString(byte[] ByteArray, UInt32 Offset, UInt32 Length) + { + byte[] Bytes = new byte[Length]; + Buffer.BlockCopy(ByteArray, (int)Offset, Bytes, 0, (int)Length); + return System.Text.Encoding.Unicode.GetString(Bytes); + } + + internal static void WriteAsciiString(byte[] ByteArray, UInt32 Offset, string Text, UInt32? MaxBufferLength = null) + { + if (MaxBufferLength != null) + { + Array.Clear(ByteArray, (int)Offset, (int)MaxBufferLength); + } + + byte[] TextBytes = System.Text.Encoding.ASCII.GetBytes(Text); + int WriteLength = TextBytes.Length; + if (WriteLength > MaxBufferLength) + { + WriteLength = (int)MaxBufferLength; + } + + Buffer.BlockCopy(TextBytes, 0, ByteArray, (int)Offset, WriteLength); + } + + internal static void WriteUnicodeString(byte[] ByteArray, UInt32 Offset, string Text, UInt32? MaxBufferLength = null) + { + if (MaxBufferLength != null) + { + Array.Clear(ByteArray, (int)Offset, (int)MaxBufferLength); + } + + byte[] TextBytes = System.Text.Encoding.Unicode.GetBytes(Text); + int WriteLength = TextBytes.Length; + if (WriteLength > MaxBufferLength) + { + WriteLength = (int)MaxBufferLength; + } + + Buffer.BlockCopy(TextBytes, 0, ByteArray, (int)Offset, WriteLength); + } + + internal static UInt32 ReadUInt32(byte[] ByteArray, UInt32 Offset) + { + return BitConverter.ToUInt32(ByteArray, (int)Offset); + } + + internal static void WriteUInt32(byte[] ByteArray, UInt32 Offset, UInt32 Value) + { + Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 4); + } + + internal static Int32 ReadInt32(byte[] ByteArray, UInt32 Offset) + { + return BitConverter.ToInt32(ByteArray, (int)Offset); + } + + internal static void WriteInt32(byte[] ByteArray, UInt32 Offset, Int32 Value) + { + Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 4); + } + + internal static UInt16 ReadUInt16(byte[] ByteArray, UInt32 Offset) + { + return BitConverter.ToUInt16(ByteArray, (int)Offset); + } + + internal static void WriteUInt16(byte[] ByteArray, UInt32 Offset, UInt16 Value) + { + Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 2); + } + + internal static Int16 ReadInt16(byte[] ByteArray, UInt32 Offset) + { + return BitConverter.ToInt16(ByteArray, (int)Offset); + } + + internal static void WriteInt16(byte[] ByteArray, UInt32 Offset, Int16 Value) + { + Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 2); + } + + internal static byte ReadUInt8(byte[] ByteArray, UInt32 Offset) + { + return ByteArray[Offset]; + } + + internal static void WriteUInt8(byte[] ByteArray, UInt32 Offset, byte Value) + { + ByteArray[Offset] = Value; + } + + internal static UInt32 ReadUInt24(byte[] ByteArray, UInt32 Offset) + { + return (UInt32)(ByteArray[Offset] + (ByteArray[Offset + 1] << 8) + (ByteArray[Offset + 2] << 16)); + } + + internal static void WriteUInt24(byte[] ByteArray, UInt32 Offset, UInt32 Value) + { + Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 3); + } + + internal static UInt64 ReadUInt64(byte[] ByteArray, UInt32 Offset) + { + return BitConverter.ToUInt64(ByteArray, (int)Offset); + } + + internal static void WriteUInt64(byte[] ByteArray, UInt32 Offset, UInt64 Value) + { + Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 8); + } + + internal static Guid ReadGuid(byte[] ByteArray, UInt32 Offset) + { + byte[] GuidBuffer = new byte[0x10]; + Buffer.BlockCopy(ByteArray, (int)Offset, GuidBuffer, 0, 0x10); + return new Guid(GuidBuffer); + } + + internal static void WriteGuid(byte[] ByteArray, UInt32 Offset, Guid Value) + { + Buffer.BlockCopy(Value.ToByteArray(), 0, ByteArray, (int)Offset, 0x10); + } + + internal static UInt32 Align(UInt32 Base, UInt32 Offset, UInt32 Alignment) + { + if (((Offset - Base) % Alignment) == 0) + { + return Offset; + } + else + { + return ((((Offset - Base) / Alignment) + 1) * Alignment) + Base; + } + } + + internal static UInt32? FindPatternInFile(string FileName, byte[] Pattern, byte[] Mask, out byte[] OutPattern) + { + // The mask is optional. + // In the mask 0x00 means the value must match, and 0xFF means that this position is a wildcard. + + UInt32? Result = null; + + FileStream Stream = new(FileName, FileMode.Open, FileAccess.Read); + + byte[] Buffer = new byte[0x10000 + Pattern.Length - 1]; + UInt32 BufferReadPosition = 0; // Position in buffer where file-chunk is being read. + UInt32 BytesInBuffer = 0; + UInt32 BytesRead; + UInt32 SearchPositionFile = 0; + UInt32 SearchPositionBuffer = 0; + UInt32 BufferFileOffset = 0; // Offset in file where data from buffer is located. + bool Match = false; + int i; + + OutPattern = null; + + while (SearchPositionFile <= (Stream.Length - Pattern.Length)) + { + if ((SearchPositionBuffer + Pattern.Length) > BytesInBuffer) + { + // Need to read next chunk + if ((BytesInBuffer - SearchPositionBuffer) > 0) + { + System.Buffer.BlockCopy(Buffer, (int)SearchPositionBuffer, Buffer, 0, (int)(BytesInBuffer - SearchPositionBuffer)); + } + BufferReadPosition = BytesInBuffer - SearchPositionBuffer; + BytesInBuffer -= SearchPositionBuffer; + BufferFileOffset += SearchPositionBuffer; + SearchPositionBuffer = 0; + + BytesRead = (UInt32)Stream.Read(Buffer, (int)BufferReadPosition, Buffer.Length - (int)BufferReadPosition); + BytesInBuffer += BytesRead; + } + + Match = true; + for (i = 0; i < Pattern.Length; i++) + { + if (Buffer[SearchPositionBuffer + i] != Pattern[i]) + { + if ((Mask == null) || (Mask[i] == 0)) + { + Match = false; + break; + } + } + } + + if (Match) + { + Result = SearchPositionFile; + + OutPattern = new byte[Pattern.Length]; + System.Buffer.BlockCopy(Buffer, (int)SearchPositionBuffer, OutPattern, 0, Pattern.Length); + break; + } + + SearchPositionBuffer++; + SearchPositionFile++; + } + + Stream.Close(); + + return Result; + } + + internal static UInt32? FindAscii(byte[] SourceBuffer, string Pattern) + { + return FindPattern(SourceBuffer, System.Text.Encoding.ASCII.GetBytes(Pattern), null, null); + } + + internal static UInt32? FindUnicode(byte[] SourceBuffer, string Pattern) + { + return FindPattern(SourceBuffer, System.Text.Encoding.Unicode.GetBytes(Pattern), null, null); + } + + internal static UInt32? FindUint(byte[] SourceBuffer, UInt32 Pattern) + { + return FindPattern(SourceBuffer, BitConverter.GetBytes(Pattern), null, null); + } + + internal static UInt32? FindPattern(byte[] SourceBuffer, byte[] Pattern, byte[] Mask, byte[] OutPattern) + { + return FindPattern(SourceBuffer, 0, null, Pattern, Mask, OutPattern); + } + + internal static bool Compare(byte[] Array1, byte[] Array2) + { + return System.Collections.StructuralComparisons.StructuralEqualityComparer.Equals(Array1, Array2); + } + + internal static UInt32? FindPattern(byte[] SourceBuffer, uint SourceOffset, uint? SourceSize, byte[] Pattern, byte[] Mask, byte[] OutPattern) + { + // The mask is optional. + // In the mask 0x00 means the value must match, and 0xFF means that this position is a wildcard. + + UInt32? Result = null; + + UInt32 SearchPosition = SourceOffset; + bool Match = false; + int i; + + while ((SearchPosition <= (SourceBuffer.Length - Pattern.Length)) && ((SourceSize == null) || (SearchPosition <= (SourceOffset + SourceSize - Pattern.Length)))) + { + Match = true; + for (i = 0; i < Pattern.Length; i++) + { + if (SourceBuffer[SearchPosition + i] != Pattern[i]) + { + if ((Mask == null) || (Mask[i] == 0)) + { + Match = false; + break; + } + } + } + + if (Match) + { + Result = SearchPosition; + + if (OutPattern != null) + { + Buffer.BlockCopy(SourceBuffer, (int)SearchPosition, OutPattern, 0, Pattern.Length); + } + + break; + } + + SearchPosition++; + } + + return Result; + } + + internal static byte CalculateChecksum8(byte[] Buffer, UInt32 Offset, UInt32 Size) + { + byte Checksum = 0; + + for (UInt32 i = Offset; i < (Offset + Size); i++) + { + Checksum += Buffer[i]; + } + + return (byte)(0x100 - Checksum); + } + + internal static UInt16 CalculateChecksum16(byte[] Buffer, UInt32 Offset, UInt32 Size) + { + UInt16 Checksum = 0; + + for (UInt32 i = Offset; i < (Offset + Size - 1); i += 2) + { + Checksum += BitConverter.ToUInt16(Buffer, (int)i); + } + + return (UInt16)(0x10000 - Checksum); + } + + private static readonly UInt32[] CRC32Table = new UInt32[] { + 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 + }; + + internal static UInt32 CRC32(byte[] Input, UInt32 Offset, UInt32 Length) + { + if ((Input == null) || ((Offset + Length) > Input.Length)) + { + throw new ArgumentException(); + } + + unchecked + { + uint crc = (uint)(((uint)0) ^ (-1)); + for (var i = Offset; i < (Offset + Length); i++) + { + crc = (crc >> 8) ^ CRC32Table[(crc ^ Input[i]) & 0xFF]; + } + crc = (uint)(crc ^ (-1)); + + return crc; + } + } + } +} diff --git a/Models/FFU.cs b/WPinternals/Models/FFU.cs similarity index 97% rename from Models/FFU.cs rename to WPinternals/Models/FFU.cs index 70f5fcd..a8103f9 100644 --- a/Models/FFU.cs +++ b/WPinternals/Models/FFU.cs @@ -1,487 +1,487 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Linq; - -namespace WPinternals -{ - internal class FFU - { - internal int ChunkSize; - internal string Path; - internal byte[] SecurityHeader; - internal byte[] ImageHeader; - internal byte[] StoreHeader; - private readonly int?[] ChunkIndexes; - private FileStream FFUFile = null; - private int FileOpenCount = 0; - - internal string PlatformID; - internal GPT GPT; - - internal UInt64 TotalSize; - internal UInt64 HeaderSize; - internal UInt64 PayloadSize; - internal UInt64 TotalChunkCount; - - internal FFU(string Path) - { - this.Path = Path; - - try - { - OpenFile(); - - // Read Security Header - byte[] ShortSecurityHeader = new byte[0x20]; - FFUFile.Read(ShortSecurityHeader, 0, 0x20); - if (ByteOperations.ReadAsciiString(ShortSecurityHeader, 0x04, 0x0C) != "SignedImage ") - { - throw new BadImageFormatException(); - } - - ChunkSize = ByteOperations.ReadInt32(ShortSecurityHeader, 0x10) * 1024; - UInt32 SecurityHeaderSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x00); - UInt32 CatalogSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x18); - UInt32 HashTableSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x1C); - SecurityHeader = new byte[RoundUpToChunks(SecurityHeaderSize + CatalogSize + HashTableSize)]; - FFUFile.Seek(0, SeekOrigin.Begin); - FFUFile.Read(SecurityHeader, 0, SecurityHeader.Length); - - // Read Image Header - byte[] ShortImageHeader = new byte[0x1C]; - FFUFile.Read(ShortImageHeader, 0, 0x1C); - if (ByteOperations.ReadAsciiString(ShortImageHeader, 0x04, 0x0C) != "ImageFlash ") - { - throw new BadImageFormatException(); - } - - UInt32 ImageHeaderSize = ByteOperations.ReadUInt32(ShortImageHeader, 0x00); - UInt32 ManifestSize = ByteOperations.ReadUInt32(ShortImageHeader, 0x10); - ImageHeader = new byte[RoundUpToChunks(ImageHeaderSize + ManifestSize)]; - FFUFile.Seek(SecurityHeader.Length, SeekOrigin.Begin); - FFUFile.Read(ImageHeader, 0, ImageHeader.Length); - - // Read Store Header - byte[] ShortStoreHeader = new byte[248]; - FFUFile.Read(ShortStoreHeader, 0, 248); - PlatformID = ByteOperations.ReadAsciiString(ShortStoreHeader, 0x0C, 192).TrimEnd(new char[] { (char)0, ' ' }); - int WriteDescriptorCount = ByteOperations.ReadInt32(ShortStoreHeader, 208); - UInt32 WriteDescriptorLength = ByteOperations.ReadUInt32(ShortStoreHeader, 212); - UInt32 ValidateDescriptorLength = ByteOperations.ReadUInt32(ShortStoreHeader, 220); - StoreHeader = new byte[RoundUpToChunks(248 + WriteDescriptorLength + ValidateDescriptorLength)]; - FFUFile.Seek(SecurityHeader.Length + ImageHeader.Length, SeekOrigin.Begin); - FFUFile.Read(StoreHeader, 0, StoreHeader.Length); - - // Parse Chunk Indexes - int HighestChunkIndex = 0; - UInt32 LocationCount; - int ChunkIndex; - int ChunkCount; - int DiskAccessMethod; - UInt32 WriteDescriptorEntryOffset = 248 + ValidateDescriptorLength; - int FFUChunkIndex = 0; - for (int i = 0; i < WriteDescriptorCount; i++) - { - LocationCount = ByteOperations.ReadUInt32(StoreHeader, WriteDescriptorEntryOffset + 0x00); - ChunkCount = ByteOperations.ReadInt32(StoreHeader, WriteDescriptorEntryOffset + 0x04); - - for (int j = 0; j < LocationCount; j++) - { - DiskAccessMethod = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x08 + (j * 0x08))); - ChunkIndex = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x0C + (j * 0x08))); - - if (DiskAccessMethod == 0 && (ChunkIndex + ChunkCount - 1) > HighestChunkIndex) // 0 = From begin, 2 = From end. We ignore chunks at end of disk. These contain secondairy GPT. - { - HighestChunkIndex = ChunkIndex + ChunkCount - 1; - } - } - WriteDescriptorEntryOffset += 8 + (LocationCount * 0x08); - FFUChunkIndex += ChunkCount; - } - ChunkIndexes = new int?[HighestChunkIndex + 1]; - WriteDescriptorEntryOffset = 248 + ValidateDescriptorLength; - FFUChunkIndex = 0; - for (int i = 0; i < WriteDescriptorCount; i++) - { - LocationCount = ByteOperations.ReadUInt32(StoreHeader, WriteDescriptorEntryOffset + 0x00); - ChunkCount = ByteOperations.ReadInt32(StoreHeader, WriteDescriptorEntryOffset + 0x04); - - for (int j = 0; j < LocationCount; j++) - { - DiskAccessMethod = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x08 + (j * 0x08))); - ChunkIndex = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x0C + (j * 0x08))); - - if (DiskAccessMethod == 0) // 0 = From begin, 2 = From end. We ignore chunks at end of disk. These contain secondairy GPT. - { - for (int k = 0; k < ChunkCount; k++) - { - ChunkIndexes[ChunkIndex + k] = FFUChunkIndex + k; - } - } - } - WriteDescriptorEntryOffset += 8 + (LocationCount * 0x08); - FFUChunkIndex += ChunkCount; - } - - byte[] GPTBuffer = GetSectors(0x01, 0x21); - GPT = new GPT(GPTBuffer); - - HeaderSize = (UInt64)(SecurityHeader.Length + ImageHeader.Length + StoreHeader.Length); - - TotalChunkCount = (UInt64)FFUChunkIndex; - PayloadSize = TotalChunkCount * (UInt64)ChunkSize; - TotalSize = HeaderSize + PayloadSize; - - if (TotalSize != (UInt64)FFUFile.Length) - { - throw new WPinternalsException("Bad FFU file", "Bad FFU file: " + Path + "." + Environment.NewLine + "Expected size: " + TotalSize.ToString() + ". Actual size: " + FFUFile.Length + "."); - } - } - catch (WPinternalsException) - { - throw; - } - catch (Exception Ex) - { - throw new WPinternalsException("Bad FFU file", "Bad FFU file: " + Path + "." + Environment.NewLine + Ex.Message, Ex); - } - finally - { - CloseFile(); - } - } - - internal static bool IsFFU(string FileName) - { - bool Result = false; - - FileStream FFUFile = new(FileName, FileMode.Open, FileAccess.Read); - - byte[] Signature = new byte[0x10]; - FFUFile.Read(Signature, 0, 0x10); - - Result = ByteOperations.ReadAsciiString(Signature, 0x04, 0x0C) == "SignedImage "; - - FFUFile.Close(); - - return Result; - } - - private void OpenFile() - { - if (FFUFile == null) - { - FFUFile = new FileStream(Path, FileMode.Open, FileAccess.Read); - FileOpenCount = 0; - } - FileOpenCount++; - } - - private void CloseFile() - { - FileOpenCount--; - if (FileOpenCount == 0) - { - FFUFile.Close(); - FFUFile = null; - } - } - - private void FileSeek(long Position) - { - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/2e67ca57-3556-4275-accd-58b7df30d424/unnecessary-filestreamseek-and-setting-filestreamposition-has-huge-effect-on-performance?forum=csharpgeneral - - if (FFUFile != null && FFUFile.Position != Position) - { - FFUFile.Seek(Position, SeekOrigin.Begin); - } - } - - internal UInt32 RoundUpToChunks(UInt32 Size) - { - if ((Size % ChunkSize) > 0) - { - return (UInt32)(((Size / ChunkSize) + 1) * ChunkSize); - } - else - { - return Size; - } - } - - internal UInt32 RoundDownToChunks(UInt32 Size) - { - if ((Size % ChunkSize) > 0) - { - return (UInt32)(Size / ChunkSize * ChunkSize); - } - else - { - return Size; - } - } - - internal byte[] GetSectors(int StartSector, int SectorCount) - { - int FirstChunk = GetChunkIndexFromSectorIndex(StartSector); - int LastChunk = GetChunkIndexFromSectorIndex(StartSector + SectorCount - 1); - - byte[] Buffer = new byte[ChunkSize]; - - OpenFile(); - - byte[] Result = new byte[SectorCount * 0x200]; - - int ResultOffset = 0; - - for (int j = FirstChunk; j <= LastChunk; j++) - { - GetChunk(Buffer, j); - - int FirstSector = 0; - int LastSector = (ChunkSize / 0x200) - 1; - - if (j == FirstChunk) - { - FirstSector = GetSectorNumberInChunkFromSectorIndex(StartSector); - } - - if (j == LastChunk) - { - LastSector = GetSectorNumberInChunkFromSectorIndex(StartSector + SectorCount - 1); - } - - int Offset = FirstSector * 0x200; - int Size = (LastSector - FirstSector + 1) * 0x200; - - System.Buffer.BlockCopy(Buffer, Offset, Result, ResultOffset, Size); - - ResultOffset += Size; - } - - CloseFile(); - - return Result; - } - - internal byte[] GetPartition(string Name) - { - Partition Target = GPT.Partitions.Find(p => string.Equals(p.Name, Name, StringComparison.CurrentCultureIgnoreCase)); - if (Target == null) - { - throw new ArgumentOutOfRangeException(); - } - - return GetSectors((int)Target.FirstSector, (int)(Target.LastSector - Target.FirstSector + 1)); - } - - internal void WritePartition(string Name, string FilePath, bool Compress = false) - { - WritePartition(Name, FilePath, null, null, Compress); - } - - internal void WritePartition(string Name, string FilePath, Action ProgressUpdateCallback, bool Compress = false) - { - WritePartition(Name, FilePath, ProgressUpdateCallback, null, Compress); - } - - internal void WritePartition(string Name, string FilePath, ProgressUpdater UpdaterPerSector, bool Compress = false) - { - WritePartition(Name, FilePath, null, UpdaterPerSector, Compress); - } - - private void WritePartition(string Name, string FilePath, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector, bool Compress = false) - { - Partition Target = GPT.Partitions.Find(p => string.Equals(p.Name, Name, StringComparison.CurrentCultureIgnoreCase)); - if (Target == null) - { - throw new ArgumentOutOfRangeException(); - } - - int FirstChunk = GetChunkIndexFromSectorIndex((int)Target.FirstSector); - int LastChunk = GetChunkIndexFromSectorIndex((int)Target.LastSector); - - ProgressUpdater Updater = UpdaterPerSector; - if ((Updater == null) && (ProgressUpdateCallback != null)) - { - Updater = new ProgressUpdater(Target.LastSector - Target.FirstSector + 1, ProgressUpdateCallback); - } - - byte[] Buffer = new byte[ChunkSize]; - - OpenFile(); - - FileStream OutputFile = new(FilePath, FileMode.Create, FileAccess.Write); - Stream OutStream = OutputFile; - - // We use gzip compression - // - // LZMA is about 60 times slower (compression is twice as good, but compressed size is already really small, so it doesnt matter much) - // OutStream = new LZMACompressionStream(OutputFile, System.IO.Compression.CompressionMode.Compress, false); - // - // DeflateStream is a raw compression stream without recognizable header - // Deflate has almost no performance penalty - // OutStream = new DeflateStream(OutputFile, CompressionLevel.Optimal, false); - // - // GZip can be recognized. It always starts with 1F 8B 08 (1F 8B is the magic value, 08 is the Deflate compression method) - // With GZip compression, dump time goes from 1m to 1m37s. So that doesnt matter much. - if (Compress) - { - OutStream = new CompressedStream(OutputFile, (Target.LastSector - Target.FirstSector + 1) * 0x200); - } - - for (int j = FirstChunk; j <= LastChunk; j++) - { - GetChunk(Buffer, j); - - int FirstSector = 0; - int LastSector = (ChunkSize / 0x200) - 1; - - if (j == FirstChunk) - { - FirstSector = GetSectorNumberInChunkFromSectorIndex((int)Target.FirstSector); - } - - if (j == LastChunk) - { - LastSector = GetSectorNumberInChunkFromSectorIndex((int)Target.LastSector); - } - - int Offset = FirstSector * 0x200; - int Size = (LastSector - FirstSector + 1) * 0x200; - - OutStream.Write(Buffer, Offset, Size); - - Updater?.IncreaseProgress((UInt64)(ChunkSize / 0x200)); - } - - OutStream.Close(); - - CloseFile(); - } - - private byte[] GetChunk(int ChunkIndex) - { - long BaseOffset = (long)SecurityHeader.Length + ImageHeader.Length + StoreHeader.Length; - if (ChunkIndexes[ChunkIndex] == null) - { - return new byte[ChunkSize]; - } - else - { - OpenFile(); - FileSeek(BaseOffset + ((long)ChunkIndexes[ChunkIndex] * ChunkSize)); - byte[] Chunk = new byte[ChunkSize]; - FFUFile.Read(Chunk, 0, ChunkSize); - CloseFile(); - return Chunk; - } - } - - private void GetChunk(byte[] Chunk, int ChunkIndex) - { - long BaseOffset = SecurityHeader.Length + ImageHeader.Length + StoreHeader.Length; - if (ChunkIndexes[ChunkIndex] == null) - { - Array.Clear(Chunk, 0, ChunkSize); - } - else - { - OpenFile(); - FileSeek(BaseOffset + ((long)ChunkIndexes[ChunkIndex] * ChunkSize)); - FFUFile.Read(Chunk, 0, ChunkSize); - CloseFile(); - } - } - - private int GetChunkIndexFromSectorIndex(int SectorIndex) - { - int SectorsPerChunk = ChunkSize / 0x200; - return SectorIndex / SectorsPerChunk; - } - - private int GetSectorNumberInChunkFromSectorIndex(int SectorIndex) - { - int SectorsPerChunk = ChunkSize / 0x200; - return SectorIndex % SectorsPerChunk; - } - - internal bool IsPartitionPresentInFFU(string PartitionName) - { - Partition Target = GPT.GetPartition(PartitionName); - if (Target == null) - { - throw new InvalidOperationException("Partitionname is not found!"); - } - - int ChunkIndex = GetChunkIndexFromSectorIndex((int)Target.FirstSector); - return ChunkIndexes[ChunkIndex] != null; - } - - private int GetChunkIndexFromSectorIndex(ulong p) - { - throw new NotImplementedException(); - } - - internal string GetFirmwareVersion() - { - string Result = null; - - Partition Plat = GPT.GetPartition("PLAT"); - if (Plat != null) - { - byte[] Data = GetPartition("PLAT"); - uint? Offset = ByteOperations.FindAscii(Data, "SWVERSION="); - if (Offset != null) - { - uint Start = (uint)Offset + 10; - uint Length = (uint)ByteOperations.FindPattern(Data, Start, 0x100, new byte[] { 0x00 }, null, null) - Start; - uint? Offset0D = ByteOperations.FindPattern(Data, Start, 0x100, new byte[] { 0x0D }, null, null); - if ((Offset0D != null) && (Offset0D < (Start + Length))) - { - Length = (uint)Offset0D - Start; - } - - Result = ByteOperations.ReadAsciiString(Data, Start, Length); - } - } - - return Result; - } - - internal string GetOSVersion() - { - byte[] efiesp = GetPartition("EFIESP"); - MemoryStream s = new(efiesp); - DiscUtils.Fat.FatFileSystem fs = new(s); - Stream mss = fs.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open, FileAccess.Read); - MemoryStream msms = new(); - mss.CopyTo(msms); - byte[] mobilestartup = msms.ToArray(); - Version OSVersion = PE.GetProductVersion(mobilestartup); - s.Close(); - - return OSVersion.ToString(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; + +namespace WPinternals +{ + internal class FFU + { + internal int ChunkSize; + internal string Path; + internal byte[] SecurityHeader; + internal byte[] ImageHeader; + internal byte[] StoreHeader; + private readonly int?[] ChunkIndexes; + private FileStream FFUFile = null; + private int FileOpenCount = 0; + + internal string PlatformID; + internal GPT GPT; + + internal UInt64 TotalSize; + internal UInt64 HeaderSize; + internal UInt64 PayloadSize; + internal UInt64 TotalChunkCount; + + internal FFU(string Path) + { + this.Path = Path; + + try + { + OpenFile(); + + // Read Security Header + byte[] ShortSecurityHeader = new byte[0x20]; + FFUFile.Read(ShortSecurityHeader, 0, 0x20); + if (ByteOperations.ReadAsciiString(ShortSecurityHeader, 0x04, 0x0C) != "SignedImage ") + { + throw new BadImageFormatException(); + } + + ChunkSize = ByteOperations.ReadInt32(ShortSecurityHeader, 0x10) * 1024; + UInt32 SecurityHeaderSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x00); + UInt32 CatalogSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x18); + UInt32 HashTableSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x1C); + SecurityHeader = new byte[RoundUpToChunks(SecurityHeaderSize + CatalogSize + HashTableSize)]; + FFUFile.Seek(0, SeekOrigin.Begin); + FFUFile.Read(SecurityHeader, 0, SecurityHeader.Length); + + // Read Image Header + byte[] ShortImageHeader = new byte[0x1C]; + FFUFile.Read(ShortImageHeader, 0, 0x1C); + if (ByteOperations.ReadAsciiString(ShortImageHeader, 0x04, 0x0C) != "ImageFlash ") + { + throw new BadImageFormatException(); + } + + UInt32 ImageHeaderSize = ByteOperations.ReadUInt32(ShortImageHeader, 0x00); + UInt32 ManifestSize = ByteOperations.ReadUInt32(ShortImageHeader, 0x10); + ImageHeader = new byte[RoundUpToChunks(ImageHeaderSize + ManifestSize)]; + FFUFile.Seek(SecurityHeader.Length, SeekOrigin.Begin); + FFUFile.Read(ImageHeader, 0, ImageHeader.Length); + + // Read Store Header + byte[] ShortStoreHeader = new byte[248]; + FFUFile.Read(ShortStoreHeader, 0, 248); + PlatformID = ByteOperations.ReadAsciiString(ShortStoreHeader, 0x0C, 192).TrimEnd(new char[] { (char)0, ' ' }); + int WriteDescriptorCount = ByteOperations.ReadInt32(ShortStoreHeader, 208); + UInt32 WriteDescriptorLength = ByteOperations.ReadUInt32(ShortStoreHeader, 212); + UInt32 ValidateDescriptorLength = ByteOperations.ReadUInt32(ShortStoreHeader, 220); + StoreHeader = new byte[RoundUpToChunks(248 + WriteDescriptorLength + ValidateDescriptorLength)]; + FFUFile.Seek(SecurityHeader.Length + ImageHeader.Length, SeekOrigin.Begin); + FFUFile.Read(StoreHeader, 0, StoreHeader.Length); + + // Parse Chunk Indexes + int HighestChunkIndex = 0; + UInt32 LocationCount; + int ChunkIndex; + int ChunkCount; + int DiskAccessMethod; + UInt32 WriteDescriptorEntryOffset = 248 + ValidateDescriptorLength; + int FFUChunkIndex = 0; + for (int i = 0; i < WriteDescriptorCount; i++) + { + LocationCount = ByteOperations.ReadUInt32(StoreHeader, WriteDescriptorEntryOffset + 0x00); + ChunkCount = ByteOperations.ReadInt32(StoreHeader, WriteDescriptorEntryOffset + 0x04); + + for (int j = 0; j < LocationCount; j++) + { + DiskAccessMethod = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x08 + (j * 0x08))); + ChunkIndex = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x0C + (j * 0x08))); + + if (DiskAccessMethod == 0 && (ChunkIndex + ChunkCount - 1) > HighestChunkIndex) // 0 = From begin, 2 = From end. We ignore chunks at end of disk. These contain secondairy GPT. + { + HighestChunkIndex = ChunkIndex + ChunkCount - 1; + } + } + WriteDescriptorEntryOffset += 8 + (LocationCount * 0x08); + FFUChunkIndex += ChunkCount; + } + ChunkIndexes = new int?[HighestChunkIndex + 1]; + WriteDescriptorEntryOffset = 248 + ValidateDescriptorLength; + FFUChunkIndex = 0; + for (int i = 0; i < WriteDescriptorCount; i++) + { + LocationCount = ByteOperations.ReadUInt32(StoreHeader, WriteDescriptorEntryOffset + 0x00); + ChunkCount = ByteOperations.ReadInt32(StoreHeader, WriteDescriptorEntryOffset + 0x04); + + for (int j = 0; j < LocationCount; j++) + { + DiskAccessMethod = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x08 + (j * 0x08))); + ChunkIndex = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x0C + (j * 0x08))); + + if (DiskAccessMethod == 0) // 0 = From begin, 2 = From end. We ignore chunks at end of disk. These contain secondairy GPT. + { + for (int k = 0; k < ChunkCount; k++) + { + ChunkIndexes[ChunkIndex + k] = FFUChunkIndex + k; + } + } + } + WriteDescriptorEntryOffset += 8 + (LocationCount * 0x08); + FFUChunkIndex += ChunkCount; + } + + byte[] GPTBuffer = GetSectors(0x01, 0x21); + GPT = new GPT(GPTBuffer); + + HeaderSize = (UInt64)(SecurityHeader.Length + ImageHeader.Length + StoreHeader.Length); + + TotalChunkCount = (UInt64)FFUChunkIndex; + PayloadSize = TotalChunkCount * (UInt64)ChunkSize; + TotalSize = HeaderSize + PayloadSize; + + if (TotalSize != (UInt64)FFUFile.Length) + { + throw new WPinternalsException("Bad FFU file", "Bad FFU file: " + Path + "." + Environment.NewLine + "Expected size: " + TotalSize.ToString() + ". Actual size: " + FFUFile.Length + "."); + } + } + catch (WPinternalsException) + { + throw; + } + catch (Exception Ex) + { + throw new WPinternalsException("Bad FFU file", "Bad FFU file: " + Path + "." + Environment.NewLine + Ex.Message, Ex); + } + finally + { + CloseFile(); + } + } + + internal static bool IsFFU(string FileName) + { + bool Result = false; + + FileStream FFUFile = new(FileName, FileMode.Open, FileAccess.Read); + + byte[] Signature = new byte[0x10]; + FFUFile.Read(Signature, 0, 0x10); + + Result = ByteOperations.ReadAsciiString(Signature, 0x04, 0x0C) == "SignedImage "; + + FFUFile.Close(); + + return Result; + } + + private void OpenFile() + { + if (FFUFile == null) + { + FFUFile = new FileStream(Path, FileMode.Open, FileAccess.Read); + FileOpenCount = 0; + } + FileOpenCount++; + } + + private void CloseFile() + { + FileOpenCount--; + if (FileOpenCount == 0) + { + FFUFile.Close(); + FFUFile = null; + } + } + + private void FileSeek(long Position) + { + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/2e67ca57-3556-4275-accd-58b7df30d424/unnecessary-filestreamseek-and-setting-filestreamposition-has-huge-effect-on-performance?forum=csharpgeneral + + if (FFUFile != null && FFUFile.Position != Position) + { + FFUFile.Seek(Position, SeekOrigin.Begin); + } + } + + internal UInt32 RoundUpToChunks(UInt32 Size) + { + if ((Size % ChunkSize) > 0) + { + return (UInt32)(((Size / ChunkSize) + 1) * ChunkSize); + } + else + { + return Size; + } + } + + internal UInt32 RoundDownToChunks(UInt32 Size) + { + if ((Size % ChunkSize) > 0) + { + return (UInt32)(Size / ChunkSize * ChunkSize); + } + else + { + return Size; + } + } + + internal byte[] GetSectors(int StartSector, int SectorCount) + { + int FirstChunk = GetChunkIndexFromSectorIndex(StartSector); + int LastChunk = GetChunkIndexFromSectorIndex(StartSector + SectorCount - 1); + + byte[] Buffer = new byte[ChunkSize]; + + OpenFile(); + + byte[] Result = new byte[SectorCount * 0x200]; + + int ResultOffset = 0; + + for (int j = FirstChunk; j <= LastChunk; j++) + { + GetChunk(Buffer, j); + + int FirstSector = 0; + int LastSector = (ChunkSize / 0x200) - 1; + + if (j == FirstChunk) + { + FirstSector = GetSectorNumberInChunkFromSectorIndex(StartSector); + } + + if (j == LastChunk) + { + LastSector = GetSectorNumberInChunkFromSectorIndex(StartSector + SectorCount - 1); + } + + int Offset = FirstSector * 0x200; + int Size = (LastSector - FirstSector + 1) * 0x200; + + System.Buffer.BlockCopy(Buffer, Offset, Result, ResultOffset, Size); + + ResultOffset += Size; + } + + CloseFile(); + + return Result; + } + + internal byte[] GetPartition(string Name) + { + Partition Target = GPT.Partitions.Find(p => string.Equals(p.Name, Name, StringComparison.CurrentCultureIgnoreCase)); + if (Target == null) + { + throw new ArgumentOutOfRangeException(); + } + + return GetSectors((int)Target.FirstSector, (int)(Target.LastSector - Target.FirstSector + 1)); + } + + internal void WritePartition(string Name, string FilePath, bool Compress = false) + { + WritePartition(Name, FilePath, null, null, Compress); + } + + internal void WritePartition(string Name, string FilePath, Action ProgressUpdateCallback, bool Compress = false) + { + WritePartition(Name, FilePath, ProgressUpdateCallback, null, Compress); + } + + internal void WritePartition(string Name, string FilePath, ProgressUpdater UpdaterPerSector, bool Compress = false) + { + WritePartition(Name, FilePath, null, UpdaterPerSector, Compress); + } + + private void WritePartition(string Name, string FilePath, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector, bool Compress = false) + { + Partition Target = GPT.Partitions.Find(p => string.Equals(p.Name, Name, StringComparison.CurrentCultureIgnoreCase)); + if (Target == null) + { + throw new ArgumentOutOfRangeException(); + } + + int FirstChunk = GetChunkIndexFromSectorIndex((int)Target.FirstSector); + int LastChunk = GetChunkIndexFromSectorIndex((int)Target.LastSector); + + ProgressUpdater Updater = UpdaterPerSector; + if ((Updater == null) && (ProgressUpdateCallback != null)) + { + Updater = new ProgressUpdater(Target.LastSector - Target.FirstSector + 1, ProgressUpdateCallback); + } + + byte[] Buffer = new byte[ChunkSize]; + + OpenFile(); + + FileStream OutputFile = new(FilePath, FileMode.Create, FileAccess.Write); + Stream OutStream = OutputFile; + + // We use gzip compression + // + // LZMA is about 60 times slower (compression is twice as good, but compressed size is already really small, so it doesnt matter much) + // OutStream = new LZMACompressionStream(OutputFile, System.IO.Compression.CompressionMode.Compress, false); + // + // DeflateStream is a raw compression stream without recognizable header + // Deflate has almost no performance penalty + // OutStream = new DeflateStream(OutputFile, CompressionLevel.Optimal, false); + // + // GZip can be recognized. It always starts with 1F 8B 08 (1F 8B is the magic value, 08 is the Deflate compression method) + // With GZip compression, dump time goes from 1m to 1m37s. So that doesnt matter much. + if (Compress) + { + OutStream = new CompressedStream(OutputFile, (Target.LastSector - Target.FirstSector + 1) * 0x200); + } + + for (int j = FirstChunk; j <= LastChunk; j++) + { + GetChunk(Buffer, j); + + int FirstSector = 0; + int LastSector = (ChunkSize / 0x200) - 1; + + if (j == FirstChunk) + { + FirstSector = GetSectorNumberInChunkFromSectorIndex((int)Target.FirstSector); + } + + if (j == LastChunk) + { + LastSector = GetSectorNumberInChunkFromSectorIndex((int)Target.LastSector); + } + + int Offset = FirstSector * 0x200; + int Size = (LastSector - FirstSector + 1) * 0x200; + + OutStream.Write(Buffer, Offset, Size); + + Updater?.IncreaseProgress((UInt64)(ChunkSize / 0x200)); + } + + OutStream.Close(); + + CloseFile(); + } + + private byte[] GetChunk(int ChunkIndex) + { + long BaseOffset = (long)SecurityHeader.Length + ImageHeader.Length + StoreHeader.Length; + if (ChunkIndexes[ChunkIndex] == null) + { + return new byte[ChunkSize]; + } + else + { + OpenFile(); + FileSeek(BaseOffset + ((long)ChunkIndexes[ChunkIndex] * ChunkSize)); + byte[] Chunk = new byte[ChunkSize]; + FFUFile.Read(Chunk, 0, ChunkSize); + CloseFile(); + return Chunk; + } + } + + private void GetChunk(byte[] Chunk, int ChunkIndex) + { + long BaseOffset = SecurityHeader.Length + ImageHeader.Length + StoreHeader.Length; + if (ChunkIndexes[ChunkIndex] == null) + { + Array.Clear(Chunk, 0, ChunkSize); + } + else + { + OpenFile(); + FileSeek(BaseOffset + ((long)ChunkIndexes[ChunkIndex] * ChunkSize)); + FFUFile.Read(Chunk, 0, ChunkSize); + CloseFile(); + } + } + + private int GetChunkIndexFromSectorIndex(int SectorIndex) + { + int SectorsPerChunk = ChunkSize / 0x200; + return SectorIndex / SectorsPerChunk; + } + + private int GetSectorNumberInChunkFromSectorIndex(int SectorIndex) + { + int SectorsPerChunk = ChunkSize / 0x200; + return SectorIndex % SectorsPerChunk; + } + + internal bool IsPartitionPresentInFFU(string PartitionName) + { + Partition Target = GPT.GetPartition(PartitionName); + if (Target == null) + { + throw new InvalidOperationException("Partitionname is not found!"); + } + + int ChunkIndex = GetChunkIndexFromSectorIndex((int)Target.FirstSector); + return ChunkIndexes[ChunkIndex] != null; + } + + private int GetChunkIndexFromSectorIndex(ulong p) + { + throw new NotImplementedException(); + } + + internal string GetFirmwareVersion() + { + string Result = null; + + Partition Plat = GPT.GetPartition("PLAT"); + if (Plat != null) + { + byte[] Data = GetPartition("PLAT"); + uint? Offset = ByteOperations.FindAscii(Data, "SWVERSION="); + if (Offset != null) + { + uint Start = (uint)Offset + 10; + uint Length = (uint)ByteOperations.FindPattern(Data, Start, 0x100, new byte[] { 0x00 }, null, null) - Start; + uint? Offset0D = ByteOperations.FindPattern(Data, Start, 0x100, new byte[] { 0x0D }, null, null); + if ((Offset0D != null) && (Offset0D < (Start + Length))) + { + Length = (uint)Offset0D - Start; + } + + Result = ByteOperations.ReadAsciiString(Data, Start, Length); + } + } + + return Result; + } + + internal string GetOSVersion() + { + byte[] efiesp = GetPartition("EFIESP"); + MemoryStream s = new(efiesp); + DiscUtils.Fat.FatFileSystem fs = new(s); + Stream mss = fs.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open, FileAccess.Read); + MemoryStream msms = new(); + mss.CopyTo(msms); + byte[] mobilestartup = msms.ToArray(); + Version OSVersion = PE.GetProductVersion(mobilestartup); + s.Close(); + + return OSVersion.ToString(); + } + } +} diff --git a/Models/GPT.cs b/WPinternals/Models/GPT.cs similarity index 97% rename from Models/GPT.cs rename to WPinternals/Models/GPT.cs index 7229851..2a2e622 100644 --- a/Models/GPT.cs +++ b/WPinternals/Models/GPT.cs @@ -1,712 +1,712 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Xml.Serialization; - -namespace WPinternals -{ - [XmlType("Partitions")] - public class GPT - { - private byte[] GPTBuffer; - private readonly UInt32 HeaderOffset; - private readonly UInt32 HeaderSize; - private UInt32 TableOffset; - private UInt32 TableSize; - private readonly UInt32 PartitionEntrySize; - private readonly UInt32 MaxPartitions; - internal UInt64 FirstUsableSector; - internal UInt64 LastUsableSector; - internal bool HasChanged = false; - - [XmlElement("Partition")] - public List Partitions = new(); - - public GPT() // Only for serialization - { - } - - internal GPT(byte[] GPTBuffer) - { - this.GPTBuffer = GPTBuffer; - UInt32? TempHeaderOffset = ByteOperations.FindAscii(GPTBuffer, "EFI PART"); - if (TempHeaderOffset == null) - { - throw new WPinternalsException("Bad GPT", "The GPT read isn't valid. Couldn't find the text \"EFI PART\"."); - } - - HeaderOffset = (UInt32)TempHeaderOffset; - HeaderSize = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x0C); - TableOffset = HeaderOffset + 0x200; - FirstUsableSector = ByteOperations.ReadUInt64(GPTBuffer, HeaderOffset + 0x28); - LastUsableSector = ByteOperations.ReadUInt64(GPTBuffer, HeaderOffset + 0x30); - MaxPartitions = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x50); - PartitionEntrySize = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x54); - TableSize = MaxPartitions * PartitionEntrySize; - if ((TableOffset + TableSize) > GPTBuffer.Length) - { - throw new WPinternalsException("Bad GPT", "The GPT read isn't valid. The sizes defined in the GPT header exceed the provided GPT size."); - } - - UInt32 PartitionOffset = TableOffset; - - while (PartitionOffset < (TableOffset + TableSize)) - { - string Name = ByteOperations.ReadUnicodeString(GPTBuffer, PartitionOffset + 0x38, 0x48).TrimEnd(new char[] { (char)0, ' ' }); - if (Name.Length == 0) - { - break; - } - - Partition CurrentPartition = new(); - CurrentPartition.Name = Name; - CurrentPartition.FirstSector = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x20); - CurrentPartition.LastSector = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x28); - CurrentPartition.PartitionTypeGuid = ByteOperations.ReadGuid(GPTBuffer, PartitionOffset + 0x00); - CurrentPartition.PartitionGuid = ByteOperations.ReadGuid(GPTBuffer, PartitionOffset + 0x10); - CurrentPartition.Attributes = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x30); - Partitions.Add(CurrentPartition); - PartitionOffset += PartitionEntrySize; - } - - HasChanged = false; - } - - internal Partition GetPartition(string Name) - { - return Partitions.Find(p => string.Equals(p.Name, Name, StringComparison.CurrentCultureIgnoreCase)); - } - - // Magic! - // SecureBoot hack for Bootloader Spec A starts here - internal byte[] InsertHack() - { - Partition HackPartition = Partitions.Find(p => p.Name == "HACK"); - Partition SBL1 = Partitions.Find(p => p.Name == "SBL1"); - Partition SBL2 = Partitions.Find(p => p.Name == "SBL2"); - - if ((SBL1 == null) || (SBL2 == null)) - { - throw new WPinternalsException("Bad GPT", "Can't patch GPT for the Secure Boot hack for Spec A devices. The provided GPT does not include a SBL1 and/or SBL2 partition."); - } - - if (HackPartition == null) - { - HackPartition = new Partition - { - Name = "HACK", - Attributes = SBL2.Attributes, - FirstSector = SBL1.LastSector, - LastSector = SBL1.LastSector, - - PartitionTypeGuid = SBL2.PartitionTypeGuid, - PartitionGuid = SBL2.PartitionGuid - }; - - Partitions.Add(HackPartition); - - SBL1.LastSector--; - - SBL2.PartitionTypeGuid = new Guid(new byte[] { 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 }); - SBL2.PartitionGuid = new Guid(new byte[] { 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 }); - } - - HasChanged = true; - - return Rebuild(); - } - - internal byte[] RemoveHack() - { - Partition HackPartition = Partitions.Find(p => p.Name == "HACK"); - Partition SBL1 = Partitions.Find(p => p.Name == "SBL1"); - Partition SBL2 = Partitions.Find(p => p.Name == "SBL2"); - - if ((SBL1 == null) || (SBL2 == null)) - { - throw new WPinternalsException("Bad GPT", "Can't un-patch GPT for the Secure Boot hack for Spec A devices. The provided GPT does not include a SBL1 and/or SBL2 partition."); - } - - if (HackPartition != null) - { - SBL2.PartitionTypeGuid = HackPartition.PartitionTypeGuid; - SBL2.PartitionGuid = HackPartition.PartitionGuid; - - Partitions.Remove(HackPartition); - - SBL1.LastSector++; - } - - HasChanged = true; - - return Rebuild(); - } - - internal byte[] Rebuild() - { - if (GPTBuffer == null) - { - TableSize = 0x4200; - TableOffset = 0; - GPTBuffer = new byte[TableSize]; - } - else - { - Array.Clear(GPTBuffer, (int)TableOffset, (int)TableSize); - } - - UInt32 PartitionOffset = TableOffset; - foreach (Partition CurrentPartition in Partitions) - { - ByteOperations.WriteGuid(GPTBuffer, PartitionOffset + 0x00, CurrentPartition.PartitionTypeGuid); - ByteOperations.WriteGuid(GPTBuffer, PartitionOffset + 0x10, CurrentPartition.PartitionGuid); - ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x20, CurrentPartition.FirstSector); - ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x28, CurrentPartition.LastSector); - ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x30, CurrentPartition.Attributes); - ByteOperations.WriteUnicodeString(GPTBuffer, PartitionOffset + 0x38, CurrentPartition.Name, 0x48); - - PartitionOffset += PartitionEntrySize; - } - - ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x58, ByteOperations.CRC32(GPTBuffer, TableOffset, TableSize)); - ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x10, 0); - ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x10, ByteOperations.CRC32(GPTBuffer, HeaderOffset, HeaderSize)); - - return GPTBuffer; - } - - internal void MergePartitionsFromFile(string Path, bool RoundToChunks) - { - MergePartitions(File.ReadAllText(Path), RoundToChunks); - } - - internal void MergePartitionsFromStream(Stream Partitions, bool RoundToChunks) - { - using TextReader tr = new StreamReader(Partitions); - MergePartitions(tr.ReadToEnd(), RoundToChunks); - } - - internal void MergePartitions(string Xml, bool RoundToChunks, ZipArchive Archive = null) - { - GPT GptToMerge; - if (Xml == null) - { - GptToMerge = new GPT(); - } - else - { - XmlSerializer x = new(typeof(GPT), ""); - MemoryStream s = new(System.Text.Encoding.ASCII.GetBytes(Xml)); - GptToMerge = (GPT)x.Deserialize(s); - s.Dispose(); - } - - if (Archive != null) - { - foreach (Partition NewPartition in GptToMerge.Partitions) - { - ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, NewPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(NewPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); - if (Entry == null) - { - // There is a partition entry in the xml, but this partition is not present in the archive. - - Partition OldPartition = GetPartition(NewPartition.Name); - if (OldPartition == null) - { - // The partition entry in the xml is also not present in the current partition table. - // It must have a know position and length. - - if (NewPartition.LastSector == 0) - { - throw new WPinternalsException("Unknown length for partition \"" + NewPartition.Name + "\". The last sector property is set to 0 and the partition doesn't exist on the device currently."); - } - } - else - { - // The partition entry in the xml is also present in the current partition table. - // But since the partition is not present in the archive, the partition cannot be relocated. - // If the location of the new partition is specified, it must be the same as the current partition. - - if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != OldPartition.FirstSector)) - { - throw new WPinternalsException("Incorrect location for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); - } - - if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != OldPartition.LastSector)) - { - throw new WPinternalsException("Incorrect length for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); - } - - NewPartition.FirstSector = OldPartition.FirstSector; - NewPartition.LastSector = OldPartition.LastSector; - } - } - else - { - // The partition in the xml is also present in the archive. - // If the length is specified in the xml, it must match the file in the archive. - - ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; - using (DecompressedStream DecompressedStream = new(Entry.Open())) - { - try - { - StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; - } - catch { } - } - - if (NewPartition.LastSector == 0) - { - NewPartition.SizeInSectors = StreamLengthInSectors; - } - else - { - if (NewPartition.SizeInSectors != StreamLengthInSectors) - { - throw new WPinternalsException("Inconsistent length specified for partition \"" + NewPartition.Name + "\". The provided partition in the archive does not match the length specified in the xml file."); - } - } - } - } - } - else - { - foreach (Partition NewPartition in GptToMerge.Partitions) - { - // This is a partition entry in the xml, and there is no archive. - - Partition OldPartition = GetPartition(NewPartition.Name); - if (OldPartition == null) - { - // The partition entry in the xml is also not present in the current partition table. - // It must have a known position and length. - - if (NewPartition.LastSector == 0) - { - throw new WPinternalsException("Unknown length for partition \"" + NewPartition.Name + "\". The last sector property is set to 0 and the partition doesn't exist on the device currently."); - } - } - else - { - // The partition entry in the xml is also present in the current partition table. - // But since the partition is not present in the archive, the partition cannot be relocated. - // If the location of the new partition is specified, it must be the same as the current partition. - - if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != OldPartition.FirstSector)) - { - throw new WPinternalsException("Incorrect location for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); - } - - if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != OldPartition.LastSector)) - { - throw new WPinternalsException("Incorrect length for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); - } - - NewPartition.FirstSector = OldPartition.FirstSector; - NewPartition.LastSector = OldPartition.LastSector; - } - } - } - - List DynamicPartitions = new(); - if (Archive != null) - { - // Partitions which are present in the archive, and which have no start-sector in the new GPT data (dynamic relocation), - // and which can be clustered to the end of emmc, are first removed from the existing GPT. - IEnumerable SortedPartitions = Partitions.OrderBy(p => p.FirstSector); - for (int i = SortedPartitions.Count() - 1; i >= 0; i--) - { - Partition OldPartition = SortedPartitions.ElementAt(i); - - // Present in archive? - ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, OldPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(OldPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); - if (Entry != null) - { - // Not present in new GPT or present in GPT without FirstSector? - Partition NewPartition = GptToMerge.GetPartition(OldPartition.Name); - if ((NewPartition == null) || (NewPartition.FirstSector == 0)) - { - DynamicPartitions.Insert(0, OldPartition); - this.Partitions.Remove(OldPartition); - } - else - { - break; - } - } - else - { - break; - } - } - } - - // All partitions in the new GPT data should have a start-sector and end-sector by now. - // The partitions in the new GPT data will be applied to the current partition-table. - // Existing partitions, which are overwritten by the new partitions will be removed from the existing GPT. - // Existing partition with the same name in the existing GPT is reused (guids and attribs remain, if not specified). - UInt64 LowestSector = 0; - Partition DPP = this.GetPartition("DPP"); - if (DPP != null) - { - LowestSector = DPP.LastSector + 1; - } - - foreach (Partition NewPartition in GptToMerge.Partitions) - { - // If the new partition is a dynamic partition, then skip it here. It will be added later. - if (DynamicPartitions.Select(p => p.Name).Any(n => string.Equals(n, NewPartition.Name, StringComparison.CurrentCultureIgnoreCase))) - { - continue; - } - - // Sanity check - if (NewPartition.FirstSector < LowestSector) - { - throw new WPinternalsException("Bad sector alignment for partition: " + NewPartition.Name + ". The partition is located before DPP."); - } - - Partition CurrentPartition = this.GetPartition(NewPartition.Name); - if (CurrentPartition == null) - { - CurrentPartition = new Partition - { - Name = NewPartition.Name - }; - this.Partitions.Add(CurrentPartition); - HasChanged = true; - } - - if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != CurrentPartition.FirstSector)) - { - CurrentPartition.FirstSector = NewPartition.FirstSector; - HasChanged = true; - } - if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != CurrentPartition.LastSector)) - { - CurrentPartition.LastSector = NewPartition.LastSector; - HasChanged = true; - } - if ((NewPartition.Attributes != 0) && (CurrentPartition.Attributes != NewPartition.Attributes)) - { - CurrentPartition.Attributes = NewPartition.Attributes; - HasChanged = true; - } - - if ((NewPartition.PartitionGuid != Guid.Empty) || (NewPartition.PartitionGuid != CurrentPartition.PartitionGuid)) - { - HasChanged = true; - } - - if (NewPartition.PartitionGuid != Guid.Empty) - { - CurrentPartition.PartitionGuid = NewPartition.PartitionGuid; - } - - if (CurrentPartition.PartitionGuid != Guid.Empty) - { - CurrentPartition.PartitionGuid = Guid.NewGuid(); - } - - if ((NewPartition.PartitionTypeGuid != Guid.Empty) || (NewPartition.PartitionTypeGuid != CurrentPartition.PartitionTypeGuid)) - { - HasChanged = true; - } - - if (NewPartition.PartitionTypeGuid != Guid.Empty) - { - CurrentPartition.PartitionTypeGuid = NewPartition.PartitionTypeGuid; - } - - if (CurrentPartition.PartitionTypeGuid != Guid.Empty) - { - CurrentPartition.PartitionTypeGuid = Guid.NewGuid(); - } - - for (int i = this.Partitions.Count - 1; i >= 0; i--) - { - if (this.Partitions[i] != CurrentPartition && (CurrentPartition.FirstSector <= this.Partitions[i].LastSector) && (CurrentPartition.LastSector >= this.Partitions[i].FirstSector)) - { - this.Partitions.RemoveAt(i); - HasChanged = true; - } - } - } - - if (Archive != null) - { - // All partitions listed in the archive, which are present in the existing GPT, should overwrite the existing partition. - // Check if the sizes of the partitions in the archive do not exceed the size of the partition, as listed in the GPT. - foreach (Partition OldPartition in this.Partitions) - { - ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, OldPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(OldPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); - if (Entry != null) - { - DecompressedStream DecompressedStream = new(Entry.Open()); - ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; - try - { - StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; - } - catch { } - DecompressedStream.Close(); - - UInt64 MaxPartitionSizeInSectors = OldPartition.SizeInSectors; - Partition NextPartition = this.Partitions.Where(p => p.FirstSector > OldPartition.FirstSector).OrderBy(p => p.FirstSector).FirstOrDefault(); - if (NextPartition != null) - { - MaxPartitionSizeInSectors = NextPartition.FirstSector - OldPartition.FirstSector; - } - - if (StreamLengthInSectors > MaxPartitionSizeInSectors) - { - throw new WPinternalsException("Incorrect length for partition \"" + OldPartition.Name + "\". The provided partition in the archive does not match the length specified in the xml file."); - } - - if (OldPartition.SizeInSectors != StreamLengthInSectors) - { - OldPartition.SizeInSectors = StreamLengthInSectors; - HasChanged = true; - } - } - } - - // All remaining partitions in the archive, which were listed in the original GPT, - // should be added at the end of the partition-table. - UInt64 FirstFreeSector = 0x5000; - if (this.Partitions.Count > 0) - { - FirstFreeSector = this.Partitions.Max(p => p.LastSector) + 1; - - // Always start a new partition on a new chunk (0x100 sector boundary), to be more flexible during custom flash - if (RoundToChunks && (((double)FirstFreeSector % 0x100) != 0)) - { - FirstFreeSector = (UInt64)(Math.Ceiling((double)FirstFreeSector / 0x100) * 0x100); - } - } - foreach (Partition NewPartition in DynamicPartitions) - { - if (NewPartition.FirstSector != FirstFreeSector) - { - NewPartition.FirstSector = FirstFreeSector; - HasChanged = true; - } - ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, NewPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(NewPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); - DecompressedStream DecompressedStream = new(Entry.Open()); - ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; - try - { - StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; - } - catch { } - DecompressedStream.Close(); - if (NewPartition.SizeInSectors != StreamLengthInSectors) - { - NewPartition.SizeInSectors = StreamLengthInSectors; - HasChanged = true; - } - this.Partitions.Add(NewPartition); - FirstFreeSector += StreamLengthInSectors; - - // Always start a new partition on a new chunk (0x100 sector boundary), to be more flexible during custom flash - if (RoundToChunks && (((double)FirstFreeSector % 0x100) != 0)) - { - FirstFreeSector = (UInt64)(Math.Ceiling((double)FirstFreeSector / 0x100) * 0x100); - } - } - } - - Rebuild(); - } - - internal void WritePartitions(string Path) - { - string DirPath = System.IO.Path.GetDirectoryName(Path); - if (!Directory.Exists(DirPath)) - { - Directory.CreateDirectory(DirPath); - } - - XmlSerializer x = new(typeof(GPT), ""); - - XmlSerializerNamespaces ns = new(); - ns.Add("", ""); - StreamWriter FileWriter = new(Path); - x.Serialize(FileWriter, this, ns); - FileWriter.Close(); - } - - internal static GPT ReadPartitions(string Path) - { - XmlSerializer x = new(typeof(GPT), ""); - using FileStream s = new(Path, FileMode.Open); - return (GPT)x.Deserialize(s); - } - - internal void RestoreBackupPartitions() - { - // This is necessary, because the partitions and backup-partitions can exchange. - // This may cause the startsector to be higher than the maximum allowed sector for flashing with a Lumia V1 programmer (hardcoded in programmer) - foreach (string RevisePartitionName in (List)(new(new string[] { "SBL1", "SBL2", "SBL3", "UEFI", "TZ", "RPM", "WINSECAPP" }))) - { - Partition RevisePartition = GetPartition(RevisePartitionName); - Partition ReviseBackupPartition = GetPartition("BACKUP_" + RevisePartitionName); - if ((RevisePartition != null) && (ReviseBackupPartition != null) && (RevisePartition.FirstSector > ReviseBackupPartition.FirstSector)) - { - ulong OriginalFirstSector = RevisePartition.FirstSector; - ulong OriginalLastSector = RevisePartition.LastSector; - RevisePartition.FirstSector = ReviseBackupPartition.FirstSector; - RevisePartition.LastSector = ReviseBackupPartition.LastSector; - ReviseBackupPartition.FirstSector = OriginalFirstSector; - ReviseBackupPartition.LastSector = OriginalLastSector; - - HasChanged = true; - } - - if (RevisePartition.LastSector >= 0xF400) - { - throw new WPinternalsException("Unsupported partition layout!", "The last sector of one of the BACKUP partitions defined in GPT exceeds the maximum threshold expected in order to restore BACKUP partitions to the device."); - } - } - } - } - - public class Partition - { - private UInt64 _SizeInSectors; - private UInt64 _FirstSector; - private UInt64 _LastSector; - - public string Name; // 0x48 - public Guid PartitionTypeGuid; // 0x10 - public Guid PartitionGuid; // 0x10 - [XmlIgnore] - internal UInt64 Attributes; // 0x08 - - [XmlIgnore] - internal UInt64 SizeInSectors - { - get - { - if (_SizeInSectors != 0) - { - return _SizeInSectors; - } - else - { - return LastSector - FirstSector + 1; - } - } - set - { - _SizeInSectors = value; - if (FirstSector != 0) - { - LastSector = FirstSector + _SizeInSectors - 1; - } - } - } - - [XmlIgnore] - internal UInt64 FirstSector // 0x08 - { - get - { - return _FirstSector; - } - set - { - _FirstSector = value; - if (_SizeInSectors != 0) - { - _LastSector = FirstSector + _SizeInSectors - 1; - } - } - } - - [XmlIgnore] - internal UInt64 LastSector // 0x08 - { - get - { - return _LastSector; - } - set - { - _LastSector = value; - _SizeInSectors = 0; - } - } - - [XmlIgnore] - public string Volume - { - get - { - return @"\\?\Volume" + PartitionGuid.ToString("b") + @"\"; - } - } - - [XmlElement(ElementName = "FirstSector")] - public string FirstSectorAsString - { - get - { - return "0x" + FirstSector.ToString("X16"); - } - set - { - FirstSector = Convert.ToUInt64(value, 16); - } - } - - [XmlElement(ElementName = "LastSector")] - public string LastSectorAsString - { - get - { - return "0x" + LastSector.ToString("X16"); - } - set - { - LastSector = Convert.ToUInt64(value, 16); - } - } - - [XmlElement(ElementName = "Attributes")] - public string AttributesAsString - { - get - { - return "0x" + Attributes.ToString("X16"); - } - set - { - Attributes = Convert.ToUInt64(value, 16); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Serialization; + +namespace WPinternals +{ + [XmlType("Partitions")] + public class GPT + { + private byte[] GPTBuffer; + private readonly UInt32 HeaderOffset; + private readonly UInt32 HeaderSize; + private UInt32 TableOffset; + private UInt32 TableSize; + private readonly UInt32 PartitionEntrySize; + private readonly UInt32 MaxPartitions; + internal UInt64 FirstUsableSector; + internal UInt64 LastUsableSector; + internal bool HasChanged = false; + + [XmlElement("Partition")] + public List Partitions = new(); + + public GPT() // Only for serialization + { + } + + internal GPT(byte[] GPTBuffer) + { + this.GPTBuffer = GPTBuffer; + UInt32? TempHeaderOffset = ByteOperations.FindAscii(GPTBuffer, "EFI PART"); + if (TempHeaderOffset == null) + { + throw new WPinternalsException("Bad GPT", "The GPT read isn't valid. Couldn't find the text \"EFI PART\"."); + } + + HeaderOffset = (UInt32)TempHeaderOffset; + HeaderSize = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x0C); + TableOffset = HeaderOffset + 0x200; + FirstUsableSector = ByteOperations.ReadUInt64(GPTBuffer, HeaderOffset + 0x28); + LastUsableSector = ByteOperations.ReadUInt64(GPTBuffer, HeaderOffset + 0x30); + MaxPartitions = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x50); + PartitionEntrySize = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x54); + TableSize = MaxPartitions * PartitionEntrySize; + if ((TableOffset + TableSize) > GPTBuffer.Length) + { + throw new WPinternalsException("Bad GPT", "The GPT read isn't valid. The sizes defined in the GPT header exceed the provided GPT size."); + } + + UInt32 PartitionOffset = TableOffset; + + while (PartitionOffset < (TableOffset + TableSize)) + { + string Name = ByteOperations.ReadUnicodeString(GPTBuffer, PartitionOffset + 0x38, 0x48).TrimEnd(new char[] { (char)0, ' ' }); + if (Name.Length == 0) + { + break; + } + + Partition CurrentPartition = new(); + CurrentPartition.Name = Name; + CurrentPartition.FirstSector = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x20); + CurrentPartition.LastSector = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x28); + CurrentPartition.PartitionTypeGuid = ByteOperations.ReadGuid(GPTBuffer, PartitionOffset + 0x00); + CurrentPartition.PartitionGuid = ByteOperations.ReadGuid(GPTBuffer, PartitionOffset + 0x10); + CurrentPartition.Attributes = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x30); + Partitions.Add(CurrentPartition); + PartitionOffset += PartitionEntrySize; + } + + HasChanged = false; + } + + internal Partition GetPartition(string Name) + { + return Partitions.Find(p => string.Equals(p.Name, Name, StringComparison.CurrentCultureIgnoreCase)); + } + + // Magic! + // SecureBoot hack for Bootloader Spec A starts here + internal byte[] InsertHack() + { + Partition HackPartition = Partitions.Find(p => p.Name == "HACK"); + Partition SBL1 = Partitions.Find(p => p.Name == "SBL1"); + Partition SBL2 = Partitions.Find(p => p.Name == "SBL2"); + + if ((SBL1 == null) || (SBL2 == null)) + { + throw new WPinternalsException("Bad GPT", "Can't patch GPT for the Secure Boot hack for Spec A devices. The provided GPT does not include a SBL1 and/or SBL2 partition."); + } + + if (HackPartition == null) + { + HackPartition = new Partition + { + Name = "HACK", + Attributes = SBL2.Attributes, + FirstSector = SBL1.LastSector, + LastSector = SBL1.LastSector, + + PartitionTypeGuid = SBL2.PartitionTypeGuid, + PartitionGuid = SBL2.PartitionGuid + }; + + Partitions.Add(HackPartition); + + SBL1.LastSector--; + + SBL2.PartitionTypeGuid = new Guid(new byte[] { 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 }); + SBL2.PartitionGuid = new Guid(new byte[] { 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 }); + } + + HasChanged = true; + + return Rebuild(); + } + + internal byte[] RemoveHack() + { + Partition HackPartition = Partitions.Find(p => p.Name == "HACK"); + Partition SBL1 = Partitions.Find(p => p.Name == "SBL1"); + Partition SBL2 = Partitions.Find(p => p.Name == "SBL2"); + + if ((SBL1 == null) || (SBL2 == null)) + { + throw new WPinternalsException("Bad GPT", "Can't un-patch GPT for the Secure Boot hack for Spec A devices. The provided GPT does not include a SBL1 and/or SBL2 partition."); + } + + if (HackPartition != null) + { + SBL2.PartitionTypeGuid = HackPartition.PartitionTypeGuid; + SBL2.PartitionGuid = HackPartition.PartitionGuid; + + Partitions.Remove(HackPartition); + + SBL1.LastSector++; + } + + HasChanged = true; + + return Rebuild(); + } + + internal byte[] Rebuild() + { + if (GPTBuffer == null) + { + TableSize = 0x4200; + TableOffset = 0; + GPTBuffer = new byte[TableSize]; + } + else + { + Array.Clear(GPTBuffer, (int)TableOffset, (int)TableSize); + } + + UInt32 PartitionOffset = TableOffset; + foreach (Partition CurrentPartition in Partitions) + { + ByteOperations.WriteGuid(GPTBuffer, PartitionOffset + 0x00, CurrentPartition.PartitionTypeGuid); + ByteOperations.WriteGuid(GPTBuffer, PartitionOffset + 0x10, CurrentPartition.PartitionGuid); + ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x20, CurrentPartition.FirstSector); + ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x28, CurrentPartition.LastSector); + ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x30, CurrentPartition.Attributes); + ByteOperations.WriteUnicodeString(GPTBuffer, PartitionOffset + 0x38, CurrentPartition.Name, 0x48); + + PartitionOffset += PartitionEntrySize; + } + + ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x58, ByteOperations.CRC32(GPTBuffer, TableOffset, TableSize)); + ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x10, 0); + ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x10, ByteOperations.CRC32(GPTBuffer, HeaderOffset, HeaderSize)); + + return GPTBuffer; + } + + internal void MergePartitionsFromFile(string Path, bool RoundToChunks) + { + MergePartitions(File.ReadAllText(Path), RoundToChunks); + } + + internal void MergePartitionsFromStream(Stream Partitions, bool RoundToChunks) + { + using TextReader tr = new StreamReader(Partitions); + MergePartitions(tr.ReadToEnd(), RoundToChunks); + } + + internal void MergePartitions(string Xml, bool RoundToChunks, ZipArchive Archive = null) + { + GPT GptToMerge; + if (Xml == null) + { + GptToMerge = new GPT(); + } + else + { + XmlSerializer x = new(typeof(GPT), ""); + MemoryStream s = new(System.Text.Encoding.ASCII.GetBytes(Xml)); + GptToMerge = (GPT)x.Deserialize(s); + s.Dispose(); + } + + if (Archive != null) + { + foreach (Partition NewPartition in GptToMerge.Partitions) + { + ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, NewPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(NewPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); + if (Entry == null) + { + // There is a partition entry in the xml, but this partition is not present in the archive. + + Partition OldPartition = GetPartition(NewPartition.Name); + if (OldPartition == null) + { + // The partition entry in the xml is also not present in the current partition table. + // It must have a know position and length. + + if (NewPartition.LastSector == 0) + { + throw new WPinternalsException("Unknown length for partition \"" + NewPartition.Name + "\". The last sector property is set to 0 and the partition doesn't exist on the device currently."); + } + } + else + { + // The partition entry in the xml is also present in the current partition table. + // But since the partition is not present in the archive, the partition cannot be relocated. + // If the location of the new partition is specified, it must be the same as the current partition. + + if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != OldPartition.FirstSector)) + { + throw new WPinternalsException("Incorrect location for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); + } + + if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != OldPartition.LastSector)) + { + throw new WPinternalsException("Incorrect length for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); + } + + NewPartition.FirstSector = OldPartition.FirstSector; + NewPartition.LastSector = OldPartition.LastSector; + } + } + else + { + // The partition in the xml is also present in the archive. + // If the length is specified in the xml, it must match the file in the archive. + + ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; + using (DecompressedStream DecompressedStream = new(Entry.Open())) + { + try + { + StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; + } + catch { } + } + + if (NewPartition.LastSector == 0) + { + NewPartition.SizeInSectors = StreamLengthInSectors; + } + else + { + if (NewPartition.SizeInSectors != StreamLengthInSectors) + { + throw new WPinternalsException("Inconsistent length specified for partition \"" + NewPartition.Name + "\". The provided partition in the archive does not match the length specified in the xml file."); + } + } + } + } + } + else + { + foreach (Partition NewPartition in GptToMerge.Partitions) + { + // This is a partition entry in the xml, and there is no archive. + + Partition OldPartition = GetPartition(NewPartition.Name); + if (OldPartition == null) + { + // The partition entry in the xml is also not present in the current partition table. + // It must have a known position and length. + + if (NewPartition.LastSector == 0) + { + throw new WPinternalsException("Unknown length for partition \"" + NewPartition.Name + "\". The last sector property is set to 0 and the partition doesn't exist on the device currently."); + } + } + else + { + // The partition entry in the xml is also present in the current partition table. + // But since the partition is not present in the archive, the partition cannot be relocated. + // If the location of the new partition is specified, it must be the same as the current partition. + + if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != OldPartition.FirstSector)) + { + throw new WPinternalsException("Incorrect location for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); + } + + if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != OldPartition.LastSector)) + { + throw new WPinternalsException("Incorrect length for partition \"" + NewPartition.Name + "\". A partition defined in the xml file got its boundaries updated, but as the partition isn't provided in the archive, it is not possible to relocate it."); + } + + NewPartition.FirstSector = OldPartition.FirstSector; + NewPartition.LastSector = OldPartition.LastSector; + } + } + } + + List DynamicPartitions = new(); + if (Archive != null) + { + // Partitions which are present in the archive, and which have no start-sector in the new GPT data (dynamic relocation), + // and which can be clustered to the end of emmc, are first removed from the existing GPT. + IEnumerable SortedPartitions = Partitions.OrderBy(p => p.FirstSector); + for (int i = SortedPartitions.Count() - 1; i >= 0; i--) + { + Partition OldPartition = SortedPartitions.ElementAt(i); + + // Present in archive? + ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, OldPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(OldPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); + if (Entry != null) + { + // Not present in new GPT or present in GPT without FirstSector? + Partition NewPartition = GptToMerge.GetPartition(OldPartition.Name); + if ((NewPartition == null) || (NewPartition.FirstSector == 0)) + { + DynamicPartitions.Insert(0, OldPartition); + this.Partitions.Remove(OldPartition); + } + else + { + break; + } + } + else + { + break; + } + } + } + + // All partitions in the new GPT data should have a start-sector and end-sector by now. + // The partitions in the new GPT data will be applied to the current partition-table. + // Existing partitions, which are overwritten by the new partitions will be removed from the existing GPT. + // Existing partition with the same name in the existing GPT is reused (guids and attribs remain, if not specified). + UInt64 LowestSector = 0; + Partition DPP = this.GetPartition("DPP"); + if (DPP != null) + { + LowestSector = DPP.LastSector + 1; + } + + foreach (Partition NewPartition in GptToMerge.Partitions) + { + // If the new partition is a dynamic partition, then skip it here. It will be added later. + if (DynamicPartitions.Select(p => p.Name).Any(n => string.Equals(n, NewPartition.Name, StringComparison.CurrentCultureIgnoreCase))) + { + continue; + } + + // Sanity check + if (NewPartition.FirstSector < LowestSector) + { + throw new WPinternalsException("Bad sector alignment for partition: " + NewPartition.Name + ". The partition is located before DPP."); + } + + Partition CurrentPartition = this.GetPartition(NewPartition.Name); + if (CurrentPartition == null) + { + CurrentPartition = new Partition + { + Name = NewPartition.Name + }; + this.Partitions.Add(CurrentPartition); + HasChanged = true; + } + + if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != CurrentPartition.FirstSector)) + { + CurrentPartition.FirstSector = NewPartition.FirstSector; + HasChanged = true; + } + if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != CurrentPartition.LastSector)) + { + CurrentPartition.LastSector = NewPartition.LastSector; + HasChanged = true; + } + if ((NewPartition.Attributes != 0) && (CurrentPartition.Attributes != NewPartition.Attributes)) + { + CurrentPartition.Attributes = NewPartition.Attributes; + HasChanged = true; + } + + if ((NewPartition.PartitionGuid != Guid.Empty) || (NewPartition.PartitionGuid != CurrentPartition.PartitionGuid)) + { + HasChanged = true; + } + + if (NewPartition.PartitionGuid != Guid.Empty) + { + CurrentPartition.PartitionGuid = NewPartition.PartitionGuid; + } + + if (CurrentPartition.PartitionGuid != Guid.Empty) + { + CurrentPartition.PartitionGuid = Guid.NewGuid(); + } + + if ((NewPartition.PartitionTypeGuid != Guid.Empty) || (NewPartition.PartitionTypeGuid != CurrentPartition.PartitionTypeGuid)) + { + HasChanged = true; + } + + if (NewPartition.PartitionTypeGuid != Guid.Empty) + { + CurrentPartition.PartitionTypeGuid = NewPartition.PartitionTypeGuid; + } + + if (CurrentPartition.PartitionTypeGuid != Guid.Empty) + { + CurrentPartition.PartitionTypeGuid = Guid.NewGuid(); + } + + for (int i = this.Partitions.Count - 1; i >= 0; i--) + { + if (this.Partitions[i] != CurrentPartition && (CurrentPartition.FirstSector <= this.Partitions[i].LastSector) && (CurrentPartition.LastSector >= this.Partitions[i].FirstSector)) + { + this.Partitions.RemoveAt(i); + HasChanged = true; + } + } + } + + if (Archive != null) + { + // All partitions listed in the archive, which are present in the existing GPT, should overwrite the existing partition. + // Check if the sizes of the partitions in the archive do not exceed the size of the partition, as listed in the GPT. + foreach (Partition OldPartition in this.Partitions) + { + ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, OldPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(OldPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); + if (Entry != null) + { + DecompressedStream DecompressedStream = new(Entry.Open()); + ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; + try + { + StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; + } + catch { } + DecompressedStream.Close(); + + UInt64 MaxPartitionSizeInSectors = OldPartition.SizeInSectors; + Partition NextPartition = this.Partitions.Where(p => p.FirstSector > OldPartition.FirstSector).OrderBy(p => p.FirstSector).FirstOrDefault(); + if (NextPartition != null) + { + MaxPartitionSizeInSectors = NextPartition.FirstSector - OldPartition.FirstSector; + } + + if (StreamLengthInSectors > MaxPartitionSizeInSectors) + { + throw new WPinternalsException("Incorrect length for partition \"" + OldPartition.Name + "\". The provided partition in the archive does not match the length specified in the xml file."); + } + + if (OldPartition.SizeInSectors != StreamLengthInSectors) + { + OldPartition.SizeInSectors = StreamLengthInSectors; + HasChanged = true; + } + } + } + + // All remaining partitions in the archive, which were listed in the original GPT, + // should be added at the end of the partition-table. + UInt64 FirstFreeSector = 0x5000; + if (this.Partitions.Count > 0) + { + FirstFreeSector = this.Partitions.Max(p => p.LastSector) + 1; + + // Always start a new partition on a new chunk (0x100 sector boundary), to be more flexible during custom flash + if (RoundToChunks && (((double)FirstFreeSector % 0x100) != 0)) + { + FirstFreeSector = (UInt64)(Math.Ceiling((double)FirstFreeSector / 0x100) * 0x100); + } + } + foreach (Partition NewPartition in DynamicPartitions) + { + if (NewPartition.FirstSector != FirstFreeSector) + { + NewPartition.FirstSector = FirstFreeSector; + HasChanged = true; + } + ZipArchiveEntry Entry = Archive.Entries.FirstOrDefault(e => string.Equals(e.Name, NewPartition.Name, StringComparison.CurrentCultureIgnoreCase) || e.Name.StartsWith(NewPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))); + DecompressedStream DecompressedStream = new(Entry.Open()); + ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; + try + { + StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; + } + catch { } + DecompressedStream.Close(); + if (NewPartition.SizeInSectors != StreamLengthInSectors) + { + NewPartition.SizeInSectors = StreamLengthInSectors; + HasChanged = true; + } + this.Partitions.Add(NewPartition); + FirstFreeSector += StreamLengthInSectors; + + // Always start a new partition on a new chunk (0x100 sector boundary), to be more flexible during custom flash + if (RoundToChunks && (((double)FirstFreeSector % 0x100) != 0)) + { + FirstFreeSector = (UInt64)(Math.Ceiling((double)FirstFreeSector / 0x100) * 0x100); + } + } + } + + Rebuild(); + } + + internal void WritePartitions(string Path) + { + string DirPath = System.IO.Path.GetDirectoryName(Path); + if (!Directory.Exists(DirPath)) + { + Directory.CreateDirectory(DirPath); + } + + XmlSerializer x = new(typeof(GPT), ""); + + XmlSerializerNamespaces ns = new(); + ns.Add("", ""); + StreamWriter FileWriter = new(Path); + x.Serialize(FileWriter, this, ns); + FileWriter.Close(); + } + + internal static GPT ReadPartitions(string Path) + { + XmlSerializer x = new(typeof(GPT), ""); + using FileStream s = new(Path, FileMode.Open); + return (GPT)x.Deserialize(s); + } + + internal void RestoreBackupPartitions() + { + // This is necessary, because the partitions and backup-partitions can exchange. + // This may cause the startsector to be higher than the maximum allowed sector for flashing with a Lumia V1 programmer (hardcoded in programmer) + foreach (string RevisePartitionName in (List)(new(new string[] { "SBL1", "SBL2", "SBL3", "UEFI", "TZ", "RPM", "WINSECAPP" }))) + { + Partition RevisePartition = GetPartition(RevisePartitionName); + Partition ReviseBackupPartition = GetPartition("BACKUP_" + RevisePartitionName); + if ((RevisePartition != null) && (ReviseBackupPartition != null) && (RevisePartition.FirstSector > ReviseBackupPartition.FirstSector)) + { + ulong OriginalFirstSector = RevisePartition.FirstSector; + ulong OriginalLastSector = RevisePartition.LastSector; + RevisePartition.FirstSector = ReviseBackupPartition.FirstSector; + RevisePartition.LastSector = ReviseBackupPartition.LastSector; + ReviseBackupPartition.FirstSector = OriginalFirstSector; + ReviseBackupPartition.LastSector = OriginalLastSector; + + HasChanged = true; + } + + if (RevisePartition.LastSector >= 0xF400) + { + throw new WPinternalsException("Unsupported partition layout!", "The last sector of one of the BACKUP partitions defined in GPT exceeds the maximum threshold expected in order to restore BACKUP partitions to the device."); + } + } + } + } + + public class Partition + { + private UInt64 _SizeInSectors; + private UInt64 _FirstSector; + private UInt64 _LastSector; + + public string Name; // 0x48 + public Guid PartitionTypeGuid; // 0x10 + public Guid PartitionGuid; // 0x10 + [XmlIgnore] + internal UInt64 Attributes; // 0x08 + + [XmlIgnore] + internal UInt64 SizeInSectors + { + get + { + if (_SizeInSectors != 0) + { + return _SizeInSectors; + } + else + { + return LastSector - FirstSector + 1; + } + } + set + { + _SizeInSectors = value; + if (FirstSector != 0) + { + LastSector = FirstSector + _SizeInSectors - 1; + } + } + } + + [XmlIgnore] + internal UInt64 FirstSector // 0x08 + { + get + { + return _FirstSector; + } + set + { + _FirstSector = value; + if (_SizeInSectors != 0) + { + _LastSector = FirstSector + _SizeInSectors - 1; + } + } + } + + [XmlIgnore] + internal UInt64 LastSector // 0x08 + { + get + { + return _LastSector; + } + set + { + _LastSector = value; + _SizeInSectors = 0; + } + } + + [XmlIgnore] + public string Volume + { + get + { + return @"\\?\Volume" + PartitionGuid.ToString("b") + @"\"; + } + } + + [XmlElement(ElementName = "FirstSector")] + public string FirstSectorAsString + { + get + { + return "0x" + FirstSector.ToString("X16"); + } + set + { + FirstSector = Convert.ToUInt64(value, 16); + } + } + + [XmlElement(ElementName = "LastSector")] + public string LastSectorAsString + { + get + { + return "0x" + LastSector.ToString("X16"); + } + set + { + LastSector = Convert.ToUInt64(value, 16); + } + } + + [XmlElement(ElementName = "Attributes")] + public string AttributesAsString + { + get + { + return "0x" + Attributes.ToString("X16"); + } + set + { + Attributes = Convert.ToUInt64(value, 16); + } + } + } +} diff --git a/Models/LZMA.cs b/WPinternals/Models/LZMA.cs similarity index 97% rename from Models/LZMA.cs rename to WPinternals/Models/LZMA.cs index 8c8c7d4..3c79335 100644 --- a/Models/LZMA.cs +++ b/WPinternals/Models/LZMA.cs @@ -1,312 +1,312 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -// SevenZip LZMA SDK: http://www.7-zip.org/download.html -// Usage: http://stackoverflow.com/questions/7646328/how-to-use-the-7z-sdk-to-compress-and-decompress-a-file - -using SevenZip; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Text; -using System.Threading; - -namespace WPinternals -{ - internal static class LZMA - { - internal static byte[] Decompress(byte[] Input, UInt32 Offset, UInt32 InputSize) - { - byte[] Properties = new byte[5]; - Buffer.BlockCopy(Input, (int)Offset, Properties, 0, 5); - - UInt64 OutputSize = ByteOperations.ReadUInt64(Input, Offset + 5); - - SevenZip.Compression.LZMA.Decoder Coder = new(); - Coder.SetDecoderProperties(Properties); - - MemoryStream InStream = new(Input, (int)Offset + 0x0D, (int)InputSize - 0x0D); - - byte[] Output = new byte[OutputSize]; - MemoryStream OutStream = new(Output, true); - - Coder.Code(InStream, OutStream, (Int64)InputSize - 0x0D, (Int64)OutputSize, null); - - OutStream.Flush(); - OutStream.Close(); - InStream.Close(); - - return Output; - } - - internal static byte[] Compress(byte[] Input, UInt32 Offset, UInt32 InputSize) - { - SevenZip.Compression.LZMA.Encoder Coder = new(); - - MemoryStream InStream = new(Input, (int)Offset, (int)InputSize); - MemoryStream OutStream = new(); - - // Write the encoder properties - Coder.WriteCoderProperties(OutStream); - - // Write the decompressed file size - OutStream.Write(BitConverter.GetBytes(InStream.Length), 0, 8); - - // Encode the file - Coder.Code(InStream, OutStream, InputSize, -1, null); - - byte[] Output = new byte[OutStream.Length]; - Buffer.BlockCopy(OutStream.GetBuffer(), 0, Output, 0, (int)OutStream.Length); - - OutStream.Flush(); - OutStream.Close(); - InStream.Close(); - - return Output; - } - } - - public class LZMACompressionStream : Stream - { - private readonly SevenZip.Compression.LZMA.Encoder Encoder = null; - private readonly SevenZip.Compression.LZMA.Decoder Decoder = null; - private readonly PumpStream BufferStream; - private readonly Stream stream; - private readonly bool LeaveOpen; - private readonly Thread WorkThread; - private readonly CancellationTokenSource source; - private readonly CancellationToken token; - - public LZMACompressionStream(Stream stream, CompressionMode mode, bool LeaveOpen, int DictionarySize, int PosStateBits, - int LitContextBits, int LitPosBits, int Algorithm, int NumFastBytes, string MatchFinder, bool EndMarker) - { - this.stream = stream; - this.LeaveOpen = LeaveOpen; - BufferStream = new PumpStream(); - source = new CancellationTokenSource(); - token = source.Token; - - if (mode == CompressionMode.Compress) - { - Encoder = new SevenZip.Compression.LZMA.Encoder(); - if (DictionarySize != 0) - { - Encoder.SetCoderProperties( - new CoderPropID[8] {CoderPropID.DictionarySize, CoderPropID.PosStateBits, CoderPropID.LitContextBits, - CoderPropID.LitPosBits, CoderPropID.Algorithm, CoderPropID.NumFastBytes, CoderPropID.MatchFinder, CoderPropID.EndMarker}, - new object[8] { DictionarySize, PosStateBits, LitContextBits, LitPosBits, Algorithm, NumFastBytes, MatchFinder, EndMarker }); - } - - Encoder.WriteCoderProperties(stream); - WorkThread = new Thread(new ThreadStart(Encode)); - } - else - { - byte[] DecoderProperties = new byte[5]; - stream.Read(DecoderProperties, 0, 5); - Decoder = new SevenZip.Compression.LZMA.Decoder(); - Decoder.SetDecoderProperties(DecoderProperties); - WorkThread = new Thread(new ThreadStart(Decode)); - } - - WorkThread.Start(); - } - - public LZMACompressionStream(Stream stream, CompressionMode mode, bool LeaveOpen) - : this(stream, mode, LeaveOpen, 0, 0, 0, 0, 0, 0, null, false) - { - } - - private void Encode() - { - Encoder.Code(BufferStream, stream, -1, -1, null, token); - if (!LeaveOpen) - { - stream.Close(); - } - } - - private void Decode() - { - Decoder.Code(stream, BufferStream, -1, -1, null, token); - BufferStream.Close(); - if (!LeaveOpen) - { - stream.Close(); - } - } - - public override void Close() - { - if (Encoder != null) - { - BufferStream.Close(); - } - else if (WorkThread.IsAlive) - { - source?.Cancel(); - WorkThread.Join(); - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - return BufferStream.Read(buffer, offset, count); - } - - public override void Write(byte[] buffer, int offset, int count) - { - BufferStream.Write(buffer, offset, count); - } - - public override bool CanRead { get { return Decoder != null; } } - public override bool CanSeek { get { return false; } } - public override bool CanWrite { get { return Encoder != null; } } - public override void Flush() { } - public override long Length { get { return 0; } } - public override long Position { get { return 0; } set { } } - public override long Seek(long offset, SeekOrigin origin) { return 0; } - public override void SetLength(long value) { } - } - - public class PumpStream : Stream - { - private readonly Queue BufferQueue; - private int BufferOffset; - private readonly long MaxBufferSize; - private long BufferSize; - private bool Closed; - private bool EOF; - - public PumpStream(long MaxBufferSize, int ReadTimeout, int WriteTimeout) - { - this.MaxBufferSize = MaxBufferSize; - this.ReadTimeout = ReadTimeout; - this.WriteTimeout = WriteTimeout; - BufferQueue = new Queue(); - BufferOffset = 0; - BufferSize = 0; - Closed = false; - EOF = false; - } - - public PumpStream() - : this(16777216, Timeout.Infinite, Timeout.Infinite) - { - } - - public new void Dispose() - { - BufferQueue.Clear(); - } - - public override void Close() - { - Closed = true; - lock (BufferQueue) - { - Monitor.Pulse(BufferQueue); - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - int BytesRead = 0; - lock (BufferQueue) - { - while (BytesRead < count && !EOF) - { - if (BufferQueue.Count > 0) - { - byte[] b = BufferQueue.Peek(); - - if ((b.Length - BufferOffset) <= (count - BytesRead)) - { - Array.Copy(b, BufferOffset, buffer, offset + BytesRead, b.Length - BufferOffset); - - BufferQueue.Dequeue(); - BufferSize -= b.Length; - Monitor.Pulse(BufferQueue); - - BytesRead += b.Length - BufferOffset; - BufferOffset = 0; - } - else - { - Array.Copy(b, BufferOffset, buffer, offset + BytesRead, count - BytesRead); - - BufferOffset += count - BytesRead; - BytesRead += count - BytesRead; - } - } - else - { - if (!Closed) - { - if (!Monitor.Wait(BufferQueue, ReadTimeout)) - { - throw new IOException("Could not read from stream: Timeout expired waiting for data to be written."); - } - } - else - { - EOF = true; - } - } - } - } - - return BytesRead; - } - - public override void Write(byte[] buffer, int offset, int count) - { - lock (BufferQueue) - { - while (BufferSize >= MaxBufferSize) - { - if (!Monitor.Wait(BufferQueue, WriteTimeout)) - { - throw new IOException("Could not write to stream: Timeout expired waiting for data to be read."); - } - } - - byte[] b = new byte[count]; - Array.Copy(buffer, offset, b, 0, count); - BufferQueue.Enqueue(b); - BufferSize += b.Length; - - Monitor.Pulse(BufferQueue); - } - } - - public override int ReadTimeout { get; set; } - public override int WriteTimeout { get; set; } - public override bool CanRead { get { return true; } } - public override bool CanSeek { get { return false; } } - public override bool CanWrite { get { return true; } } - public override void Flush() { } - public override long Length { get { return 0; } } - public override long Position { get { return 0; } set { } } - public override long Seek(long offset, SeekOrigin origin) { return 0; } - public override void SetLength(long value) { } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// SevenZip LZMA SDK: http://www.7-zip.org/download.html +// Usage: http://stackoverflow.com/questions/7646328/how-to-use-the-7z-sdk-to-compress-and-decompress-a-file + +using SevenZip; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Threading; + +namespace WPinternals +{ + internal static class LZMA + { + internal static byte[] Decompress(byte[] Input, UInt32 Offset, UInt32 InputSize) + { + byte[] Properties = new byte[5]; + Buffer.BlockCopy(Input, (int)Offset, Properties, 0, 5); + + UInt64 OutputSize = ByteOperations.ReadUInt64(Input, Offset + 5); + + SevenZip.Compression.LZMA.Decoder Coder = new(); + Coder.SetDecoderProperties(Properties); + + MemoryStream InStream = new(Input, (int)Offset + 0x0D, (int)InputSize - 0x0D); + + byte[] Output = new byte[OutputSize]; + MemoryStream OutStream = new(Output, true); + + Coder.Code(InStream, OutStream, (Int64)InputSize - 0x0D, (Int64)OutputSize, null); + + OutStream.Flush(); + OutStream.Close(); + InStream.Close(); + + return Output; + } + + internal static byte[] Compress(byte[] Input, UInt32 Offset, UInt32 InputSize) + { + SevenZip.Compression.LZMA.Encoder Coder = new(); + + MemoryStream InStream = new(Input, (int)Offset, (int)InputSize); + MemoryStream OutStream = new(); + + // Write the encoder properties + Coder.WriteCoderProperties(OutStream); + + // Write the decompressed file size + OutStream.Write(BitConverter.GetBytes(InStream.Length), 0, 8); + + // Encode the file + Coder.Code(InStream, OutStream, InputSize, -1, null); + + byte[] Output = new byte[OutStream.Length]; + Buffer.BlockCopy(OutStream.GetBuffer(), 0, Output, 0, (int)OutStream.Length); + + OutStream.Flush(); + OutStream.Close(); + InStream.Close(); + + return Output; + } + } + + public class LZMACompressionStream : Stream + { + private readonly SevenZip.Compression.LZMA.Encoder Encoder = null; + private readonly SevenZip.Compression.LZMA.Decoder Decoder = null; + private readonly PumpStream BufferStream; + private readonly Stream stream; + private readonly bool LeaveOpen; + private readonly Thread WorkThread; + private readonly CancellationTokenSource source; + private readonly CancellationToken token; + + public LZMACompressionStream(Stream stream, CompressionMode mode, bool LeaveOpen, int DictionarySize, int PosStateBits, + int LitContextBits, int LitPosBits, int Algorithm, int NumFastBytes, string MatchFinder, bool EndMarker) + { + this.stream = stream; + this.LeaveOpen = LeaveOpen; + BufferStream = new PumpStream(); + source = new CancellationTokenSource(); + token = source.Token; + + if (mode == CompressionMode.Compress) + { + Encoder = new SevenZip.Compression.LZMA.Encoder(); + if (DictionarySize != 0) + { + Encoder.SetCoderProperties( + new CoderPropID[8] {CoderPropID.DictionarySize, CoderPropID.PosStateBits, CoderPropID.LitContextBits, + CoderPropID.LitPosBits, CoderPropID.Algorithm, CoderPropID.NumFastBytes, CoderPropID.MatchFinder, CoderPropID.EndMarker}, + new object[8] { DictionarySize, PosStateBits, LitContextBits, LitPosBits, Algorithm, NumFastBytes, MatchFinder, EndMarker }); + } + + Encoder.WriteCoderProperties(stream); + WorkThread = new Thread(new ThreadStart(Encode)); + } + else + { + byte[] DecoderProperties = new byte[5]; + stream.Read(DecoderProperties, 0, 5); + Decoder = new SevenZip.Compression.LZMA.Decoder(); + Decoder.SetDecoderProperties(DecoderProperties); + WorkThread = new Thread(new ThreadStart(Decode)); + } + + WorkThread.Start(); + } + + public LZMACompressionStream(Stream stream, CompressionMode mode, bool LeaveOpen) + : this(stream, mode, LeaveOpen, 0, 0, 0, 0, 0, 0, null, false) + { + } + + private void Encode() + { + Encoder.Code(BufferStream, stream, -1, -1, null, token); + if (!LeaveOpen) + { + stream.Close(); + } + } + + private void Decode() + { + Decoder.Code(stream, BufferStream, -1, -1, null, token); + BufferStream.Close(); + if (!LeaveOpen) + { + stream.Close(); + } + } + + public override void Close() + { + if (Encoder != null) + { + BufferStream.Close(); + } + else if (WorkThread.IsAlive) + { + source?.Cancel(); + WorkThread.Join(); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + return BufferStream.Read(buffer, offset, count); + } + + public override void Write(byte[] buffer, int offset, int count) + { + BufferStream.Write(buffer, offset, count); + } + + public override bool CanRead { get { return Decoder != null; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return Encoder != null; } } + public override void Flush() { } + public override long Length { get { return 0; } } + public override long Position { get { return 0; } set { } } + public override long Seek(long offset, SeekOrigin origin) { return 0; } + public override void SetLength(long value) { } + } + + public class PumpStream : Stream + { + private readonly Queue BufferQueue; + private int BufferOffset; + private readonly long MaxBufferSize; + private long BufferSize; + private bool Closed; + private bool EOF; + + public PumpStream(long MaxBufferSize, int ReadTimeout, int WriteTimeout) + { + this.MaxBufferSize = MaxBufferSize; + this.ReadTimeout = ReadTimeout; + this.WriteTimeout = WriteTimeout; + BufferQueue = new Queue(); + BufferOffset = 0; + BufferSize = 0; + Closed = false; + EOF = false; + } + + public PumpStream() + : this(16777216, Timeout.Infinite, Timeout.Infinite) + { + } + + public new void Dispose() + { + BufferQueue.Clear(); + } + + public override void Close() + { + Closed = true; + lock (BufferQueue) + { + Monitor.Pulse(BufferQueue); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + int BytesRead = 0; + lock (BufferQueue) + { + while (BytesRead < count && !EOF) + { + if (BufferQueue.Count > 0) + { + byte[] b = BufferQueue.Peek(); + + if ((b.Length - BufferOffset) <= (count - BytesRead)) + { + Array.Copy(b, BufferOffset, buffer, offset + BytesRead, b.Length - BufferOffset); + + BufferQueue.Dequeue(); + BufferSize -= b.Length; + Monitor.Pulse(BufferQueue); + + BytesRead += b.Length - BufferOffset; + BufferOffset = 0; + } + else + { + Array.Copy(b, BufferOffset, buffer, offset + BytesRead, count - BytesRead); + + BufferOffset += count - BytesRead; + BytesRead += count - BytesRead; + } + } + else + { + if (!Closed) + { + if (!Monitor.Wait(BufferQueue, ReadTimeout)) + { + throw new IOException("Could not read from stream: Timeout expired waiting for data to be written."); + } + } + else + { + EOF = true; + } + } + } + } + + return BytesRead; + } + + public override void Write(byte[] buffer, int offset, int count) + { + lock (BufferQueue) + { + while (BufferSize >= MaxBufferSize) + { + if (!Monitor.Wait(BufferQueue, WriteTimeout)) + { + throw new IOException("Could not write to stream: Timeout expired waiting for data to be read."); + } + } + + byte[] b = new byte[count]; + Array.Copy(buffer, offset, b, 0, count); + BufferQueue.Enqueue(b); + BufferSize += b.Length; + + Monitor.Pulse(BufferQueue); + } + } + + public override int ReadTimeout { get; set; } + public override int WriteTimeout { get; set; } + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return true; } } + public override void Flush() { } + public override long Length { get { return 0; } } + public override long Position { get { return 0; } set { } } + public override long Seek(long offset, SeekOrigin origin) { return 0; } + public override void SetLength(long value) { } + } +} diff --git a/Models/LumiaDownloadModel.cs b/WPinternals/Models/LumiaDownloadModel.cs similarity index 97% rename from Models/LumiaDownloadModel.cs rename to WPinternals/Models/LumiaDownloadModel.cs index a726574..1995acf 100644 --- a/Models/LumiaDownloadModel.cs +++ b/WPinternals/Models/LumiaDownloadModel.cs @@ -1,860 +1,860 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Serialization; - -namespace WPinternals -{ - internal static class LumiaDownloadModel - { - internal static string SearchFFU(string ProductType, string ProductCode, string OperatorCode) - { - return SearchFFU(ProductType, ProductCode, OperatorCode, out string FoundProductType); - } - - internal static string SearchFFU(string ProductType, string ProductCode, string OperatorCode, out string FoundProductType) - { - if (ProductType?.Length == 0) - { - ProductType = null; - } - - if (ProductCode?.Length == 0) - { - ProductCode = null; - } - - if (OperatorCode?.Length == 0) - { - OperatorCode = null; - } - - if (ProductCode != null) - { - ProductCode = ProductCode.ToUpper(); - ProductType = null; - OperatorCode = null; - } - if (ProductType != null) - { - ProductType = ProductType.ToUpper(); - if (ProductType.StartsWith("RM") && !ProductType.StartsWith("RM-")) - { - ProductType = "RM-" + ProductType[2..]; - } - } - if (OperatorCode != null) - { - OperatorCode = OperatorCode.ToUpper(); - } - - DiscoveryQueryParameters DiscoveryQueryParams = new() - { - manufacturerName = "Microsoft", - manufacturerProductLine = "Lumia", - packageType = "Firmware", - packageClass = "Public", - manufacturerHardwareModel = ProductType, - manufacturerHardwareVariant = ProductCode, - operatorName = OperatorCode - }; - DiscoveryParameters DiscoveryParams = new() - { - query = DiscoveryQueryParams - }; - - DataContractJsonSerializer Serializer1 = new(typeof(DiscoveryParameters)); - MemoryStream JsonStream1 = new(); - Serializer1.WriteObject(JsonStream1, DiscoveryParams); - JsonStream1.Seek(0L, SeekOrigin.Begin); - string JsonContent = new StreamReader(JsonStream1).ReadToEnd(); - - Uri RequestUri = new("https://api.swrepository.com/rest-api/discovery/1/package"); - - HttpClient HttpClient = new(); - HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("SoftwareRepository"); - HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - Task HttpPostTask = HttpClient.PostAsync(RequestUri, new StringContent(JsonContent, Encoding.UTF8, "application/json")); - HttpPostTask.Wait(); - HttpResponseMessage Response = HttpPostTask.Result; - - string JsonResultString = ""; - if (Response.StatusCode == HttpStatusCode.OK) - { - Task ReadResponseTask = Response.Content.ReadAsStringAsync(); - ReadResponseTask.Wait(); - JsonResultString = ReadResponseTask.Result; - } - - SoftwarePackage Package = null; - using (MemoryStream JsonStream2 = new(Encoding.UTF8.GetBytes(JsonResultString))) - { - DataContractJsonSerializer Serializer2 = new(typeof(SoftwarePackages)); - SoftwarePackages SoftwarePackages = (SoftwarePackages)Serializer2.ReadObject(JsonStream2); - if (SoftwarePackages != null) - { - Package = SoftwarePackages.softwarePackages.FirstOrDefault(); - } - } - - if (Package == null) - { - throw new WPinternalsException("FFU not found", "No FFU has been found in the remote software repository for the requested model."); - } - - FoundProductType = Package.manufacturerHardwareModel[0]; - - SoftwareFile FileInfo = Package.files.First(f => f.fileName.EndsWith(".ffu", StringComparison.OrdinalIgnoreCase)); - - Uri FileInfoUri = new("https://api.swrepository.com/rest-api/discovery/fileurl/1/" + Package.id + "/" + FileInfo.fileName); - Task GetFileInfoTask = HttpClient.GetStringAsync(FileInfoUri); - GetFileInfoTask.Wait(); - string FileInfoString = GetFileInfoTask.Result; - - string FfuUrl = ""; - FileUrlResult FileUrl = null; - using (MemoryStream JsonStream3 = new(Encoding.UTF8.GetBytes(FileInfoString))) - { - DataContractJsonSerializer Serializer3 = new(typeof(FileUrlResult)); - FileUrl = (FileUrlResult)Serializer3.ReadObject(JsonStream3); - if (FileUrl != null) - { - FfuUrl = FileUrl.url.Replace("sr.azureedge.net", "softwarerepo.blob.core.windows.net"); - } - } - - HttpClient.Dispose(); - - return FfuUrl; - } - - internal static string SearchENOSW(string ProductType, string PhoneFirmwareRevision) - { - if (ProductType?.Length == 0) - { - ProductType = null; - } - - if (ProductType != null) - { - ProductType = ProductType.ToUpper(); - if (ProductType.StartsWith("RM") && !ProductType.StartsWith("RM-")) - { - ProductType = "RM-" + ProductType[2..]; - } - } - - DiscoveryQueryParameters DiscoveryQueryParams = new() - { - manufacturerName = "Microsoft", - manufacturerProductLine = "Lumia", - packageType = "Test Mode", - packageClass = "Public", - manufacturerHardwareModel = ProductType - }; - DiscoveryParameters DiscoveryParams = new() - { - query = DiscoveryQueryParams - }; - - DataContractJsonSerializer Serializer1 = new(typeof(DiscoveryParameters)); - MemoryStream JsonStream1 = new(); - Serializer1.WriteObject(JsonStream1, DiscoveryParams); - JsonStream1.Seek(0L, SeekOrigin.Begin); - string JsonContent = new StreamReader(JsonStream1).ReadToEnd(); - - Uri RequestUri = new("https://api.swrepository.com/rest-api/discovery/1/package"); - - HttpClient HttpClient = new(); - HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("SoftwareRepository"); - HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - Task HttpPostTask = HttpClient.PostAsync(RequestUri, new StringContent(JsonContent, Encoding.UTF8, "application/json")); - HttpPostTask.Wait(); - HttpResponseMessage Response = HttpPostTask.Result; - - string JsonResultString = ""; - if (Response.StatusCode == HttpStatusCode.OK) - { - Task ReadResponseTask = Response.Content.ReadAsStringAsync(); - ReadResponseTask.Wait(); - JsonResultString = ReadResponseTask.Result; - } - - SoftwarePackage Package = null; - using (MemoryStream JsonStream2 = new(Encoding.UTF8.GetBytes(JsonResultString))) - { - DataContractJsonSerializer Serializer2 = new(typeof(SoftwarePackages)); - SoftwarePackages SoftwarePackages = (SoftwarePackages)Serializer2.ReadObject(JsonStream2); - if (SoftwarePackages != null) - { - foreach (SoftwarePackage pkg in SoftwarePackages.softwarePackages) - { - Package = SoftwarePackages.softwarePackages.FirstOrDefault(); - } - } - } - - if (Package == null) - { - throw new WPinternalsException("ENOSW package not found", "No ENOSW package has been found in the remote software repository for the requested model."); - } - - SoftwareFile FileInfo = Package.files.First(f => f.fileName.EndsWith(".secwim", StringComparison.OrdinalIgnoreCase)); - - SoftwareFile DPLF = Package.files.First(f => f.fileName.EndsWith(".dpl", StringComparison.OrdinalIgnoreCase)); - Uri DPLUri = new("https://api.swrepository.com/rest-api/discovery/fileurl/1/" + Package.id + "/" + DPLF.fileName); - Task GetDPLTask = HttpClient.GetStringAsync(DPLUri); - GetDPLTask.Wait(); - string DPLString = GetDPLTask.Result; - - string DPLUrl = ""; - FileUrlResult FileUrlDPL = null; - using (MemoryStream JsonStream3 = new(Encoding.UTF8.GetBytes(DPLString))) - { - DataContractJsonSerializer Serializer3 = new(typeof(FileUrlResult)); - FileUrlDPL = (FileUrlResult)Serializer3.ReadObject(JsonStream3); - if (FileUrlDPL != null) - { - DPLUrl = FileUrlDPL.url.Replace("sr.azureedge.net", "softwarerepo.blob.core.windows.net"); - } - } - - if (DPLUrl?.Length == 0) - { - throw new WPinternalsException("DPL not found", "No DPL has been found in the remote software repository for the requested model."); - } - - Task GetDPLStrTask = HttpClient.GetStringAsync(DPLUrl); - GetDPLStrTask.Wait(); - string DPLStrString = GetDPLStrTask.Result; - - DPL.Package dpl; - XmlSerializer serializer = new(typeof(DPL.Package)); - using (StringReader reader = new(DPLStrString.Replace("ft:", "").Replace("dpl:", "").Replace("typedes:", ""))) - { - dpl = (DPL.Package)serializer.Deserialize(reader); - } - - foreach (DPL.File file in dpl.Content.Files.File) - { - string name = file.Name; - - DPL.Range range = file.Extensions.MmosWimFile.UseCaseCompatibilities.Compatibility.FirstOrDefault().Range; - - if (IsFirmwareBetween(PhoneFirmwareRevision, range.From, range.To)) - { - FileInfo = Package.files.First(f => f.fileName.EndsWith(name, StringComparison.OrdinalIgnoreCase)); - } - } - - Uri FileInfoUri = new("https://api.swrepository.com/rest-api/discovery/fileurl/1/" + Package.id + "/" + FileInfo.fileName); - Task GetFileInfoTask = HttpClient.GetStringAsync(FileInfoUri); - GetFileInfoTask.Wait(); - string FileInfoString = GetFileInfoTask.Result; - - string ENOSWUrl = ""; - FileUrlResult FileUrl = null; - using (MemoryStream JsonStream3 = new(Encoding.UTF8.GetBytes(FileInfoString))) - { - DataContractJsonSerializer Serializer3 = new(typeof(FileUrlResult)); - FileUrl = (FileUrlResult)Serializer3.ReadObject(JsonStream3); - if (FileUrl != null) - { - ENOSWUrl = FileUrl.url.Replace("sr.azureedge.net", "softwarerepo.blob.core.windows.net"); - } - } - - HttpClient.Dispose(); - - return ENOSWUrl; - } - - private static bool IsFirmwareBetween(string PhoneFirmwareRevision, string Limit1, string Limit2) - { - var version = new Version(PhoneFirmwareRevision); - var version1 = new Version(Limit1); - var version2 = new Version(Limit2); - - var result = version.CompareTo(version1); - var result2 = version.CompareTo(version2); - - return result >= 0 && result2 <= 0; - } - - internal static string[] SearchEmergencyFiles(string ProductType) - { - ProductType = ProductType.ToUpper(); - if (ProductType.StartsWith("RM") && !ProductType.StartsWith("RM-")) - { - ProductType = "RM-" + ProductType[2..]; - } - - LogFile.Log("Getting Emergency files for: " + ProductType, LogType.FileAndConsole); - - if ((ProductType == "RM-1072") || (ProductType == "RM-1073")) - { - LogFile.Log("Due to mix-up in online-repository, redirecting to emergency files of RM-1113", LogType.FileAndConsole); - ProductType = "RM-1113"; - } - - List Result = new(); - - WebClient Client = new(); - string Src; - string FileName; - string Config = null; - try - { - Config = Client.DownloadString("https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/emergency_flash_config.xml"); - } - catch - { - LogFile.Log("Emergency files for " + ProductType + " not found", LogType.FileAndConsole); - return null; - } - Client.Dispose(); - - XmlDocument Doc = new(); - Doc.LoadXml(Config); - - // Hex - XmlNode Node = Doc.SelectSingleNode("//emergency_flash_config/hex_flasher"); - if (Node != null) - { - FileName = Node.Attributes["image_path"].InnerText; - Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; - LogFile.Log("Hex-file: " + Src); - Result.Add(Src); - } - - // Mbn - Node = Doc.SelectSingleNode("//emergency_flash_config/mbn_image"); - if (Node != null) - { - FileName = Node.Attributes["image_path"].InnerText; - Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; - LogFile.Log("Mbn-file: " + Src); - Result.Add(Src); - } - - // Ede - foreach (XmlNode SubNode in Doc.SelectNodes("//emergency_flash_config/first_boot_images/first_boot_image")) - { - FileName = SubNode.Attributes["image_path"].InnerText; - Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; - LogFile.Log("Firehose-programmer-file: " + Src); - Result.Add(Src); - } - - // Edp - foreach (XmlNode SubNode in Doc.SelectNodes("//emergency_flash_config/second_boot_firehose_single_image/firehose_image")) - { - FileName = SubNode.Attributes["image_path"].InnerText; - Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; - LogFile.Log("Firehose-payload-file: " + Src); - Result.Add(Src); - } - - return Result.ToArray(); - } - } - -#pragma warning disable 0649 - [DataContract] - internal class FileUrlResult - { - [DataMember] - internal string url; - - [DataMember] - internal List alternateUrl; - - [DataMember] - internal long fileSize; - - [DataMember] - internal List checksum; - } -#pragma warning restore 0649 - - [DataContract] - public class DiscoveryQueryParameters - { - [DataMember(EmitDefaultValue = false)] - public string customerName; - - [DataMember(EmitDefaultValue = false)] - public ExtendedAttributes extendedAttributes; - - [DataMember(EmitDefaultValue = false)] - public string manufacturerHardwareModel; - - [DataMember(EmitDefaultValue = false)] - public string manufacturerHardwareVariant; - - [DataMember(EmitDefaultValue = false)] - public string manufacturerModelName; - - [DataMember] - public string manufacturerName; - - [DataMember(EmitDefaultValue = false)] - public string manufacturerPackageId; - - [DataMember(EmitDefaultValue = false)] - public string manufacturerPlatformId; - - [DataMember] - public string manufacturerProductLine; - - [DataMember(EmitDefaultValue = false)] - public string manufacturerVariantName; - - [DataMember(EmitDefaultValue = false)] - public string operatorName; - - [DataMember] - public string packageClass; - - [DataMember(EmitDefaultValue = false)] - public string packageRevision; - - [DataMember(EmitDefaultValue = false)] - public string packageState; - - [DataMember(EmitDefaultValue = false)] - public string packageSubRevision; - - [DataMember(EmitDefaultValue = false)] - public string packageSubtitle; - - [DataMember(EmitDefaultValue = false)] - public string packageTitle; - - [DataMember] - public string packageType; - } - - [DataContract] - public class DiscoveryParameters - { - [DataMember(Name = "api-version")] - public string apiVersion; - - [DataMember] - public DiscoveryQueryParameters query; - - [DataMember] - public List condition; - - [DataMember] - public List response; - - public DiscoveryParameters() : this(DiscoveryCondition.Default) - { - } - - public DiscoveryParameters(DiscoveryCondition Condition) - { - this.apiVersion = "1"; - this.query = new DiscoveryQueryParameters(); - this.condition = new List(); - if (Condition == DiscoveryCondition.All) - { - this.condition.Add("all"); - return; - } - if (Condition == DiscoveryCondition.Latest) - { - this.condition.Add("latest"); - return; - } - this.condition.Add("default"); - } - } - - public enum DiscoveryCondition - { - Default, - All, - Latest - } - - [Serializable] - public class ExtendedAttributes : ISerializable - { - public Dictionary Dictionary; - - public ExtendedAttributes() - { - this.Dictionary = new Dictionary(); - } - - protected ExtendedAttributes(SerializationInfo info, StreamingContext context) - { - if (info != null) - { - this.Dictionary = new Dictionary(); - SerializationInfoEnumerator Enumerator = info.GetEnumerator(); - while (Enumerator.MoveNext()) - { - this.Dictionary.Add(Enumerator.Current.Name, (string)Enumerator.Current.Value); - } - } - } - - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info != null) - { - foreach (string Current in this.Dictionary.Keys) - { - info.AddValue(Current, this.Dictionary[Current]); - } - } - } - } - - [DataContract] - public class SoftwareFileChecksum - { - [DataMember] - public string type; - - [DataMember] - public string value; - } - - [DataContract] - public class SoftwarePackages - { - [DataMember] - public List softwarePackages; - } - - [DataContract] - public class SoftwarePackage - { - [DataMember] - public List customerName; - - [DataMember] - public ExtendedAttributes extendedAttributes; - - [DataMember] - public List files; - - [DataMember] - public string id; - - [DataMember] - public List manufacturerHardwareModel; - - [DataMember] - public List manufacturerHardwareVariant; - - [DataMember] - public List manufacturerModelName; - - [DataMember] - public string manufacturerName; - - [DataMember] - public string manufacturerPackageId; - - [DataMember] - public List manufacturerPlatformId; - - [DataMember] - public string manufacturerProductLine; - - [DataMember] - public List manufacturerVariantName; - - [DataMember] - public List operatorName; - - [DataMember] - public List packageClass; - - [DataMember] - public string packageDescription; - - [DataMember] - public string packageRevision; - - [DataMember] - public string packageState; - - [DataMember] - public string packageSubRevision; - - [DataMember] - public string packageSubtitle; - - [DataMember] - public string packageTitle; - - [DataMember] - public string packageType; - } - - [DataContract] - public class SoftwareFile - { - [DataMember] - public List checksum; - - [DataMember] - public string fileName; - - [DataMember] - public long fileSize; - - [DataMember] - public string fileType; - } - - public static class DPL - { - [XmlRoot(ElementName = "BasicProductCodes")] - public class BasicProductCodes - { - [XmlElement(ElementName = "BasicProductCode")] - public List BasicProductCode { get; set; } - } - - [XmlRoot(ElementName = "Identification")] - public class Identification - { - [XmlElement(ElementName = "TypeDesignator")] - public string TypeDesignator { get; set; } - [XmlElement(ElementName = "BasicProductCodes")] - public BasicProductCodes BasicProductCodes { get; set; } - [XmlElement(ElementName = "Purpose")] - public string Purpose { get; set; } - } - - [XmlRoot(ElementName = "Extensions")] - public class Extensions - { - [XmlElement(ElementName = "PackageType")] - public string PackageType { get; set; } - [XmlElement(ElementName = "Identification")] - public Identification Identification { get; set; } - [XmlElement(ElementName = "FileType")] - public string FileType { get; set; } - [XmlElement(ElementName = "MmosWimFile")] - public MmosWimFile MmosWimFile { get; set; } - } - - [XmlRoot(ElementName = "PackageDescription")] - public class PackageDescription - { - [XmlElement(ElementName = "Identifier")] - public string Identifier { get; set; } - [XmlElement(ElementName = "Revision")] - public string Revision { get; set; } - [XmlElement(ElementName = "Extensions")] - public Extensions Extensions { get; set; } - } - - [XmlRoot(ElementName = "Digest")] - public class Digest - { - [XmlAttribute(AttributeName = "method")] - public string Method { get; set; } - [XmlAttribute(AttributeName = "encoding")] - public string Encoding { get; set; } - [XmlText] - public string Text { get; set; } - } - - [XmlRoot(ElementName = "Digests")] - public class Digests - { - [XmlElement(ElementName = "Digest")] - public List Digest { get; set; } - } - - [XmlRoot(ElementName = "Range")] - public class Range - { - [XmlAttribute(AttributeName = "from")] - public string From { get; set; } - [XmlAttribute(AttributeName = "to")] - public string To { get; set; } - } - - [XmlRoot(ElementName = "Compatibility")] - public class Compatibility - { - [XmlElement(ElementName = "Range")] - public Range Range { get; set; } - [XmlAttribute(AttributeName = "useCase")] - public string UseCase { get; set; } - } - - [XmlRoot(ElementName = "UseCaseCompatibilities")] - public class UseCaseCompatibilities - { - [XmlElement(ElementName = "Compatibility")] - public List Compatibility { get; set; } - } - - [XmlRoot(ElementName = "MmosWimFile")] - public class MmosWimFile - { - [XmlElement(ElementName = "UseCaseCompatibilities")] - public UseCaseCompatibilities UseCaseCompatibilities { get; set; } - } - - [XmlRoot(ElementName = "File")] - public class File - { - [XmlElement(ElementName = "Name")] - public string Name { get; set; } - [XmlElement(ElementName = "Digests")] - public Digests Digests { get; set; } - [XmlElement(ElementName = "Revision")] - public string Revision { get; set; } - [XmlElement(ElementName = "Extensions")] - public Extensions Extensions { get; set; } - } - - [XmlRoot(ElementName = "Files")] - public class Files - { - [XmlElement(ElementName = "File")] - public List File { get; set; } - } - - [XmlRoot(ElementName = "Content")] - public class Content - { - [XmlElement(ElementName = "PackageDescription")] - public PackageDescription PackageDescription { get; set; } - [XmlElement(ElementName = "Files")] - public Files Files { get; set; } - } - - [XmlRoot(ElementName = "CanonicalizationMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class CanonicalizationMethod - { - [XmlAttribute(AttributeName = "Algorithm")] - public string Algorithm { get; set; } - } - - [XmlRoot(ElementName = "SignatureMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class SignatureMethod - { - [XmlAttribute(AttributeName = "Algorithm")] - public string Algorithm { get; set; } - } - - [XmlRoot(ElementName = "Transform", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class Transform - { - [XmlAttribute(AttributeName = "Algorithm")] - public string Algorithm { get; set; } - } - - [XmlRoot(ElementName = "Transforms", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class Transforms - { - [XmlElement(ElementName = "Transform", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public Transform Transform { get; set; } - } - - [XmlRoot(ElementName = "DigestMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class DigestMethod - { - [XmlAttribute(AttributeName = "Algorithm")] - public string Algorithm { get; set; } - } - - [XmlRoot(ElementName = "Reference", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class Reference - { - [XmlElement(ElementName = "Transforms", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public Transforms Transforms { get; set; } - [XmlElement(ElementName = "DigestMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public DigestMethod DigestMethod { get; set; } - [XmlElement(ElementName = "DigestValue", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public string DigestValue { get; set; } - [XmlAttribute(AttributeName = "URI")] - public string URI { get; set; } - } - - [XmlRoot(ElementName = "SignedInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class SignedInfo - { - [XmlElement(ElementName = "CanonicalizationMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public CanonicalizationMethod CanonicalizationMethod { get; set; } - [XmlElement(ElementName = "SignatureMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public SignatureMethod SignatureMethod { get; set; } - [XmlElement(ElementName = "Reference", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public Reference Reference { get; set; } - } - - [XmlRoot(ElementName = "KeyInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class KeyInfo - { - [XmlElement(ElementName = "KeyName", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public string KeyName { get; set; } - } - - [XmlRoot(ElementName = "Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public class Signature - { - [XmlElement(ElementName = "SignedInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public SignedInfo SignedInfo { get; set; } - [XmlElement(ElementName = "SignatureValue", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public string SignatureValue { get; set; } - [XmlElement(ElementName = "KeyInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public KeyInfo KeyInfo { get; set; } - [XmlAttribute(AttributeName = "xmlns")] - public string Xmlns { get; set; } - } - - [XmlRoot(ElementName = "Package")] - public class Package - { - [XmlElement(ElementName = "Content")] - public Content Content { get; set; } - [XmlElement(ElementName = "Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#")] - public Signature Signature { get; set; } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace WPinternals +{ + internal static class LumiaDownloadModel + { + internal static string SearchFFU(string ProductType, string ProductCode, string OperatorCode) + { + return SearchFFU(ProductType, ProductCode, OperatorCode, out string FoundProductType); + } + + internal static string SearchFFU(string ProductType, string ProductCode, string OperatorCode, out string FoundProductType) + { + if (ProductType?.Length == 0) + { + ProductType = null; + } + + if (ProductCode?.Length == 0) + { + ProductCode = null; + } + + if (OperatorCode?.Length == 0) + { + OperatorCode = null; + } + + if (ProductCode != null) + { + ProductCode = ProductCode.ToUpper(); + ProductType = null; + OperatorCode = null; + } + if (ProductType != null) + { + ProductType = ProductType.ToUpper(); + if (ProductType.StartsWith("RM") && !ProductType.StartsWith("RM-")) + { + ProductType = "RM-" + ProductType[2..]; + } + } + if (OperatorCode != null) + { + OperatorCode = OperatorCode.ToUpper(); + } + + DiscoveryQueryParameters DiscoveryQueryParams = new() + { + manufacturerName = "Microsoft", + manufacturerProductLine = "Lumia", + packageType = "Firmware", + packageClass = "Public", + manufacturerHardwareModel = ProductType, + manufacturerHardwareVariant = ProductCode, + operatorName = OperatorCode + }; + DiscoveryParameters DiscoveryParams = new() + { + query = DiscoveryQueryParams + }; + + DataContractJsonSerializer Serializer1 = new(typeof(DiscoveryParameters)); + MemoryStream JsonStream1 = new(); + Serializer1.WriteObject(JsonStream1, DiscoveryParams); + JsonStream1.Seek(0L, SeekOrigin.Begin); + string JsonContent = new StreamReader(JsonStream1).ReadToEnd(); + + Uri RequestUri = new("https://api.swrepository.com/rest-api/discovery/1/package"); + + HttpClient HttpClient = new(); + HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("SoftwareRepository"); + HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + Task HttpPostTask = HttpClient.PostAsync(RequestUri, new StringContent(JsonContent, Encoding.UTF8, "application/json")); + HttpPostTask.Wait(); + HttpResponseMessage Response = HttpPostTask.Result; + + string JsonResultString = ""; + if (Response.StatusCode == HttpStatusCode.OK) + { + Task ReadResponseTask = Response.Content.ReadAsStringAsync(); + ReadResponseTask.Wait(); + JsonResultString = ReadResponseTask.Result; + } + + SoftwarePackage Package = null; + using (MemoryStream JsonStream2 = new(Encoding.UTF8.GetBytes(JsonResultString))) + { + DataContractJsonSerializer Serializer2 = new(typeof(SoftwarePackages)); + SoftwarePackages SoftwarePackages = (SoftwarePackages)Serializer2.ReadObject(JsonStream2); + if (SoftwarePackages != null) + { + Package = SoftwarePackages.softwarePackages.FirstOrDefault(); + } + } + + if (Package == null) + { + throw new WPinternalsException("FFU not found", "No FFU has been found in the remote software repository for the requested model."); + } + + FoundProductType = Package.manufacturerHardwareModel[0]; + + SoftwareFile FileInfo = Package.files.First(f => f.fileName.EndsWith(".ffu", StringComparison.OrdinalIgnoreCase)); + + Uri FileInfoUri = new("https://api.swrepository.com/rest-api/discovery/fileurl/1/" + Package.id + "/" + FileInfo.fileName); + Task GetFileInfoTask = HttpClient.GetStringAsync(FileInfoUri); + GetFileInfoTask.Wait(); + string FileInfoString = GetFileInfoTask.Result; + + string FfuUrl = ""; + FileUrlResult FileUrl = null; + using (MemoryStream JsonStream3 = new(Encoding.UTF8.GetBytes(FileInfoString))) + { + DataContractJsonSerializer Serializer3 = new(typeof(FileUrlResult)); + FileUrl = (FileUrlResult)Serializer3.ReadObject(JsonStream3); + if (FileUrl != null) + { + FfuUrl = FileUrl.url.Replace("sr.azureedge.net", "softwarerepo.blob.core.windows.net"); + } + } + + HttpClient.Dispose(); + + return FfuUrl; + } + + internal static string SearchENOSW(string ProductType, string PhoneFirmwareRevision) + { + if (ProductType?.Length == 0) + { + ProductType = null; + } + + if (ProductType != null) + { + ProductType = ProductType.ToUpper(); + if (ProductType.StartsWith("RM") && !ProductType.StartsWith("RM-")) + { + ProductType = "RM-" + ProductType[2..]; + } + } + + DiscoveryQueryParameters DiscoveryQueryParams = new() + { + manufacturerName = "Microsoft", + manufacturerProductLine = "Lumia", + packageType = "Test Mode", + packageClass = "Public", + manufacturerHardwareModel = ProductType + }; + DiscoveryParameters DiscoveryParams = new() + { + query = DiscoveryQueryParams + }; + + DataContractJsonSerializer Serializer1 = new(typeof(DiscoveryParameters)); + MemoryStream JsonStream1 = new(); + Serializer1.WriteObject(JsonStream1, DiscoveryParams); + JsonStream1.Seek(0L, SeekOrigin.Begin); + string JsonContent = new StreamReader(JsonStream1).ReadToEnd(); + + Uri RequestUri = new("https://api.swrepository.com/rest-api/discovery/1/package"); + + HttpClient HttpClient = new(); + HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("SoftwareRepository"); + HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + Task HttpPostTask = HttpClient.PostAsync(RequestUri, new StringContent(JsonContent, Encoding.UTF8, "application/json")); + HttpPostTask.Wait(); + HttpResponseMessage Response = HttpPostTask.Result; + + string JsonResultString = ""; + if (Response.StatusCode == HttpStatusCode.OK) + { + Task ReadResponseTask = Response.Content.ReadAsStringAsync(); + ReadResponseTask.Wait(); + JsonResultString = ReadResponseTask.Result; + } + + SoftwarePackage Package = null; + using (MemoryStream JsonStream2 = new(Encoding.UTF8.GetBytes(JsonResultString))) + { + DataContractJsonSerializer Serializer2 = new(typeof(SoftwarePackages)); + SoftwarePackages SoftwarePackages = (SoftwarePackages)Serializer2.ReadObject(JsonStream2); + if (SoftwarePackages != null) + { + foreach (SoftwarePackage pkg in SoftwarePackages.softwarePackages) + { + Package = SoftwarePackages.softwarePackages.FirstOrDefault(); + } + } + } + + if (Package == null) + { + throw new WPinternalsException("ENOSW package not found", "No ENOSW package has been found in the remote software repository for the requested model."); + } + + SoftwareFile FileInfo = Package.files.First(f => f.fileName.EndsWith(".secwim", StringComparison.OrdinalIgnoreCase)); + + SoftwareFile DPLF = Package.files.First(f => f.fileName.EndsWith(".dpl", StringComparison.OrdinalIgnoreCase)); + Uri DPLUri = new("https://api.swrepository.com/rest-api/discovery/fileurl/1/" + Package.id + "/" + DPLF.fileName); + Task GetDPLTask = HttpClient.GetStringAsync(DPLUri); + GetDPLTask.Wait(); + string DPLString = GetDPLTask.Result; + + string DPLUrl = ""; + FileUrlResult FileUrlDPL = null; + using (MemoryStream JsonStream3 = new(Encoding.UTF8.GetBytes(DPLString))) + { + DataContractJsonSerializer Serializer3 = new(typeof(FileUrlResult)); + FileUrlDPL = (FileUrlResult)Serializer3.ReadObject(JsonStream3); + if (FileUrlDPL != null) + { + DPLUrl = FileUrlDPL.url.Replace("sr.azureedge.net", "softwarerepo.blob.core.windows.net"); + } + } + + if (DPLUrl?.Length == 0) + { + throw new WPinternalsException("DPL not found", "No DPL has been found in the remote software repository for the requested model."); + } + + Task GetDPLStrTask = HttpClient.GetStringAsync(DPLUrl); + GetDPLStrTask.Wait(); + string DPLStrString = GetDPLStrTask.Result; + + DPL.Package dpl; + XmlSerializer serializer = new(typeof(DPL.Package)); + using (StringReader reader = new(DPLStrString.Replace("ft:", "").Replace("dpl:", "").Replace("typedes:", ""))) + { + dpl = (DPL.Package)serializer.Deserialize(reader); + } + + foreach (DPL.File file in dpl.Content.Files.File) + { + string name = file.Name; + + DPL.Range range = file.Extensions.MmosWimFile.UseCaseCompatibilities.Compatibility.FirstOrDefault().Range; + + if (IsFirmwareBetween(PhoneFirmwareRevision, range.From, range.To)) + { + FileInfo = Package.files.First(f => f.fileName.EndsWith(name, StringComparison.OrdinalIgnoreCase)); + } + } + + Uri FileInfoUri = new("https://api.swrepository.com/rest-api/discovery/fileurl/1/" + Package.id + "/" + FileInfo.fileName); + Task GetFileInfoTask = HttpClient.GetStringAsync(FileInfoUri); + GetFileInfoTask.Wait(); + string FileInfoString = GetFileInfoTask.Result; + + string ENOSWUrl = ""; + FileUrlResult FileUrl = null; + using (MemoryStream JsonStream3 = new(Encoding.UTF8.GetBytes(FileInfoString))) + { + DataContractJsonSerializer Serializer3 = new(typeof(FileUrlResult)); + FileUrl = (FileUrlResult)Serializer3.ReadObject(JsonStream3); + if (FileUrl != null) + { + ENOSWUrl = FileUrl.url.Replace("sr.azureedge.net", "softwarerepo.blob.core.windows.net"); + } + } + + HttpClient.Dispose(); + + return ENOSWUrl; + } + + private static bool IsFirmwareBetween(string PhoneFirmwareRevision, string Limit1, string Limit2) + { + var version = new Version(PhoneFirmwareRevision); + var version1 = new Version(Limit1); + var version2 = new Version(Limit2); + + var result = version.CompareTo(version1); + var result2 = version.CompareTo(version2); + + return result >= 0 && result2 <= 0; + } + + internal static string[] SearchEmergencyFiles(string ProductType) + { + ProductType = ProductType.ToUpper(); + if (ProductType.StartsWith("RM") && !ProductType.StartsWith("RM-")) + { + ProductType = "RM-" + ProductType[2..]; + } + + LogFile.Log("Getting Emergency files for: " + ProductType, LogType.FileAndConsole); + + if ((ProductType == "RM-1072") || (ProductType == "RM-1073")) + { + LogFile.Log("Due to mix-up in online-repository, redirecting to emergency files of RM-1113", LogType.FileAndConsole); + ProductType = "RM-1113"; + } + + List Result = new(); + + WebClient Client = new(); + string Src; + string FileName; + string Config = null; + try + { + Config = Client.DownloadString("https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/emergency_flash_config.xml"); + } + catch + { + LogFile.Log("Emergency files for " + ProductType + " not found", LogType.FileAndConsole); + return null; + } + Client.Dispose(); + + XmlDocument Doc = new(); + Doc.LoadXml(Config); + + // Hex + XmlNode Node = Doc.SelectSingleNode("//emergency_flash_config/hex_flasher"); + if (Node != null) + { + FileName = Node.Attributes["image_path"].InnerText; + Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; + LogFile.Log("Hex-file: " + Src); + Result.Add(Src); + } + + // Mbn + Node = Doc.SelectSingleNode("//emergency_flash_config/mbn_image"); + if (Node != null) + { + FileName = Node.Attributes["image_path"].InnerText; + Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; + LogFile.Log("Mbn-file: " + Src); + Result.Add(Src); + } + + // Ede + foreach (XmlNode SubNode in Doc.SelectNodes("//emergency_flash_config/first_boot_images/first_boot_image")) + { + FileName = SubNode.Attributes["image_path"].InnerText; + Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; + LogFile.Log("Firehose-programmer-file: " + Src); + Result.Add(Src); + } + + // Edp + foreach (XmlNode SubNode in Doc.SelectNodes("//emergency_flash_config/second_boot_firehose_single_image/firehose_image")) + { + FileName = SubNode.Attributes["image_path"].InnerText; + Src = "https://repairavoidance.blob.core.windows.net/packages/EmergencyFlash/" + ProductType + "/" + FileName; + LogFile.Log("Firehose-payload-file: " + Src); + Result.Add(Src); + } + + return Result.ToArray(); + } + } + +#pragma warning disable 0649 + [DataContract] + internal class FileUrlResult + { + [DataMember] + internal string url; + + [DataMember] + internal List alternateUrl; + + [DataMember] + internal long fileSize; + + [DataMember] + internal List checksum; + } +#pragma warning restore 0649 + + [DataContract] + public class DiscoveryQueryParameters + { + [DataMember(EmitDefaultValue = false)] + public string customerName; + + [DataMember(EmitDefaultValue = false)] + public ExtendedAttributes extendedAttributes; + + [DataMember(EmitDefaultValue = false)] + public string manufacturerHardwareModel; + + [DataMember(EmitDefaultValue = false)] + public string manufacturerHardwareVariant; + + [DataMember(EmitDefaultValue = false)] + public string manufacturerModelName; + + [DataMember] + public string manufacturerName; + + [DataMember(EmitDefaultValue = false)] + public string manufacturerPackageId; + + [DataMember(EmitDefaultValue = false)] + public string manufacturerPlatformId; + + [DataMember] + public string manufacturerProductLine; + + [DataMember(EmitDefaultValue = false)] + public string manufacturerVariantName; + + [DataMember(EmitDefaultValue = false)] + public string operatorName; + + [DataMember] + public string packageClass; + + [DataMember(EmitDefaultValue = false)] + public string packageRevision; + + [DataMember(EmitDefaultValue = false)] + public string packageState; + + [DataMember(EmitDefaultValue = false)] + public string packageSubRevision; + + [DataMember(EmitDefaultValue = false)] + public string packageSubtitle; + + [DataMember(EmitDefaultValue = false)] + public string packageTitle; + + [DataMember] + public string packageType; + } + + [DataContract] + public class DiscoveryParameters + { + [DataMember(Name = "api-version")] + public string apiVersion; + + [DataMember] + public DiscoveryQueryParameters query; + + [DataMember] + public List condition; + + [DataMember] + public List response; + + public DiscoveryParameters() : this(DiscoveryCondition.Default) + { + } + + public DiscoveryParameters(DiscoveryCondition Condition) + { + this.apiVersion = "1"; + this.query = new DiscoveryQueryParameters(); + this.condition = new List(); + if (Condition == DiscoveryCondition.All) + { + this.condition.Add("all"); + return; + } + if (Condition == DiscoveryCondition.Latest) + { + this.condition.Add("latest"); + return; + } + this.condition.Add("default"); + } + } + + public enum DiscoveryCondition + { + Default, + All, + Latest + } + + [Serializable] + public class ExtendedAttributes : ISerializable + { + public Dictionary Dictionary; + + public ExtendedAttributes() + { + this.Dictionary = new Dictionary(); + } + + protected ExtendedAttributes(SerializationInfo info, StreamingContext context) + { + if (info != null) + { + this.Dictionary = new Dictionary(); + SerializationInfoEnumerator Enumerator = info.GetEnumerator(); + while (Enumerator.MoveNext()) + { + this.Dictionary.Add(Enumerator.Current.Name, (string)Enumerator.Current.Value); + } + } + } + + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info != null) + { + foreach (string Current in this.Dictionary.Keys) + { + info.AddValue(Current, this.Dictionary[Current]); + } + } + } + } + + [DataContract] + public class SoftwareFileChecksum + { + [DataMember] + public string type; + + [DataMember] + public string value; + } + + [DataContract] + public class SoftwarePackages + { + [DataMember] + public List softwarePackages; + } + + [DataContract] + public class SoftwarePackage + { + [DataMember] + public List customerName; + + [DataMember] + public ExtendedAttributes extendedAttributes; + + [DataMember] + public List files; + + [DataMember] + public string id; + + [DataMember] + public List manufacturerHardwareModel; + + [DataMember] + public List manufacturerHardwareVariant; + + [DataMember] + public List manufacturerModelName; + + [DataMember] + public string manufacturerName; + + [DataMember] + public string manufacturerPackageId; + + [DataMember] + public List manufacturerPlatformId; + + [DataMember] + public string manufacturerProductLine; + + [DataMember] + public List manufacturerVariantName; + + [DataMember] + public List operatorName; + + [DataMember] + public List packageClass; + + [DataMember] + public string packageDescription; + + [DataMember] + public string packageRevision; + + [DataMember] + public string packageState; + + [DataMember] + public string packageSubRevision; + + [DataMember] + public string packageSubtitle; + + [DataMember] + public string packageTitle; + + [DataMember] + public string packageType; + } + + [DataContract] + public class SoftwareFile + { + [DataMember] + public List checksum; + + [DataMember] + public string fileName; + + [DataMember] + public long fileSize; + + [DataMember] + public string fileType; + } + + public static class DPL + { + [XmlRoot(ElementName = "BasicProductCodes")] + public class BasicProductCodes + { + [XmlElement(ElementName = "BasicProductCode")] + public List BasicProductCode { get; set; } + } + + [XmlRoot(ElementName = "Identification")] + public class Identification + { + [XmlElement(ElementName = "TypeDesignator")] + public string TypeDesignator { get; set; } + [XmlElement(ElementName = "BasicProductCodes")] + public BasicProductCodes BasicProductCodes { get; set; } + [XmlElement(ElementName = "Purpose")] + public string Purpose { get; set; } + } + + [XmlRoot(ElementName = "Extensions")] + public class Extensions + { + [XmlElement(ElementName = "PackageType")] + public string PackageType { get; set; } + [XmlElement(ElementName = "Identification")] + public Identification Identification { get; set; } + [XmlElement(ElementName = "FileType")] + public string FileType { get; set; } + [XmlElement(ElementName = "MmosWimFile")] + public MmosWimFile MmosWimFile { get; set; } + } + + [XmlRoot(ElementName = "PackageDescription")] + public class PackageDescription + { + [XmlElement(ElementName = "Identifier")] + public string Identifier { get; set; } + [XmlElement(ElementName = "Revision")] + public string Revision { get; set; } + [XmlElement(ElementName = "Extensions")] + public Extensions Extensions { get; set; } + } + + [XmlRoot(ElementName = "Digest")] + public class Digest + { + [XmlAttribute(AttributeName = "method")] + public string Method { get; set; } + [XmlAttribute(AttributeName = "encoding")] + public string Encoding { get; set; } + [XmlText] + public string Text { get; set; } + } + + [XmlRoot(ElementName = "Digests")] + public class Digests + { + [XmlElement(ElementName = "Digest")] + public List Digest { get; set; } + } + + [XmlRoot(ElementName = "Range")] + public class Range + { + [XmlAttribute(AttributeName = "from")] + public string From { get; set; } + [XmlAttribute(AttributeName = "to")] + public string To { get; set; } + } + + [XmlRoot(ElementName = "Compatibility")] + public class Compatibility + { + [XmlElement(ElementName = "Range")] + public Range Range { get; set; } + [XmlAttribute(AttributeName = "useCase")] + public string UseCase { get; set; } + } + + [XmlRoot(ElementName = "UseCaseCompatibilities")] + public class UseCaseCompatibilities + { + [XmlElement(ElementName = "Compatibility")] + public List Compatibility { get; set; } + } + + [XmlRoot(ElementName = "MmosWimFile")] + public class MmosWimFile + { + [XmlElement(ElementName = "UseCaseCompatibilities")] + public UseCaseCompatibilities UseCaseCompatibilities { get; set; } + } + + [XmlRoot(ElementName = "File")] + public class File + { + [XmlElement(ElementName = "Name")] + public string Name { get; set; } + [XmlElement(ElementName = "Digests")] + public Digests Digests { get; set; } + [XmlElement(ElementName = "Revision")] + public string Revision { get; set; } + [XmlElement(ElementName = "Extensions")] + public Extensions Extensions { get; set; } + } + + [XmlRoot(ElementName = "Files")] + public class Files + { + [XmlElement(ElementName = "File")] + public List File { get; set; } + } + + [XmlRoot(ElementName = "Content")] + public class Content + { + [XmlElement(ElementName = "PackageDescription")] + public PackageDescription PackageDescription { get; set; } + [XmlElement(ElementName = "Files")] + public Files Files { get; set; } + } + + [XmlRoot(ElementName = "CanonicalizationMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class CanonicalizationMethod + { + [XmlAttribute(AttributeName = "Algorithm")] + public string Algorithm { get; set; } + } + + [XmlRoot(ElementName = "SignatureMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class SignatureMethod + { + [XmlAttribute(AttributeName = "Algorithm")] + public string Algorithm { get; set; } + } + + [XmlRoot(ElementName = "Transform", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class Transform + { + [XmlAttribute(AttributeName = "Algorithm")] + public string Algorithm { get; set; } + } + + [XmlRoot(ElementName = "Transforms", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class Transforms + { + [XmlElement(ElementName = "Transform", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public Transform Transform { get; set; } + } + + [XmlRoot(ElementName = "DigestMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class DigestMethod + { + [XmlAttribute(AttributeName = "Algorithm")] + public string Algorithm { get; set; } + } + + [XmlRoot(ElementName = "Reference", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class Reference + { + [XmlElement(ElementName = "Transforms", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public Transforms Transforms { get; set; } + [XmlElement(ElementName = "DigestMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public DigestMethod DigestMethod { get; set; } + [XmlElement(ElementName = "DigestValue", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public string DigestValue { get; set; } + [XmlAttribute(AttributeName = "URI")] + public string URI { get; set; } + } + + [XmlRoot(ElementName = "SignedInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class SignedInfo + { + [XmlElement(ElementName = "CanonicalizationMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public CanonicalizationMethod CanonicalizationMethod { get; set; } + [XmlElement(ElementName = "SignatureMethod", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public SignatureMethod SignatureMethod { get; set; } + [XmlElement(ElementName = "Reference", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public Reference Reference { get; set; } + } + + [XmlRoot(ElementName = "KeyInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class KeyInfo + { + [XmlElement(ElementName = "KeyName", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public string KeyName { get; set; } + } + + [XmlRoot(ElementName = "Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public class Signature + { + [XmlElement(ElementName = "SignedInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public SignedInfo SignedInfo { get; set; } + [XmlElement(ElementName = "SignatureValue", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public string SignatureValue { get; set; } + [XmlElement(ElementName = "KeyInfo", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public KeyInfo KeyInfo { get; set; } + [XmlAttribute(AttributeName = "xmlns")] + public string Xmlns { get; set; } + } + + [XmlRoot(ElementName = "Package")] + public class Package + { + [XmlElement(ElementName = "Content")] + public Content Content { get; set; } + [XmlElement(ElementName = "Signature", Namespace = "http://www.w3.org/2000/09/xmldsig#")] + public Signature Signature { get; set; } + } + } +} diff --git a/Models/MassStorage.cs b/WPinternals/Models/MassStorage.cs similarity index 97% rename from Models/MassStorage.cs rename to WPinternals/Models/MassStorage.cs index c08d88e..f49c51c 100644 --- a/Models/MassStorage.cs +++ b/WPinternals/Models/MassStorage.cs @@ -1,551 +1,551 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Linq; -using System.Management; -using System.Runtime.InteropServices; - -namespace WPinternals -{ - internal class MassStorage : NokiaPhoneModel - { - internal string Drive = null; - internal string PhysicalDrive = null; - internal string VolumeLabel = null; - internal IntPtr hVolume = (IntPtr)(-1); - internal IntPtr hDrive = (IntPtr)(-1); - private bool OpenWithWriteAccess; - - private string Serial; - - internal MassStorage(string DevicePath) : base(DevicePath) - { - try - { - foreach (ManagementObject logical in new ManagementObjectSearcher("select * from Win32_LogicalDisk").Get()) - { - System.Diagnostics.Debug.Print(logical["Name"].ToString()); - - string Label = ""; - foreach (ManagementObject partition in logical.GetRelated("Win32_DiskPartition")) - { - foreach (ManagementObject drive in partition.GetRelated("Win32_DiskDrive")) - { - if (drive["PNPDeviceID"].ToString().Contains("VEN_QUALCOMM&PROD_MMC_STORAGE", StringComparison.CurrentCulture) || - drive["PNPDeviceID"].ToString().Contains("VEN_MSFT&PROD_PHONE_MMC_STOR", StringComparison.CurrentCulture)) - { - Label = logical["VolumeName"] == null ? "" : logical["VolumeName"].ToString(); - if ((Drive == null) || string.Equals(Label, "MainOS", StringComparison.CurrentCultureIgnoreCase)) // Always prefer the MainOS drive-mapping - { - Drive = logical["Name"].ToString(); - PhysicalDrive = drive["DeviceID"].ToString(); - VolumeLabel = Label; - } - if (string.Equals(Label, "MainOS", StringComparison.CurrentCultureIgnoreCase)) - { - break; - } - } - } - if (string.Equals(Label, "MainOS", StringComparison.CurrentCultureIgnoreCase)) - { - break; - } - } - } - } - catch { } - } - - internal void AttachQualcommSerial(string DevicePath) - { - try - { - QualcommSerial SerialDevice = new(DevicePath); - SerialDevice.Close(); - SerialDevice.Dispose(); - - Serial = DevicePath; - } - catch (Exception ex) - { - LogFile.Log(ex.Message); - } - } - - internal bool DoesDeviceSupportReboot() - { - return Serial != null; - } - - internal void Reboot() - { - if (Serial == null) - { - return; - } - - try - { - QualcommSerial SerialDevice = new(Serial); - - SerialDevice.EncodeCommands = false; - - // This will succeed on new models - SerialDevice.SendData(new byte[] { 0x7, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0 }); - - // This will succeed on old models - SerialDevice.SendData(new byte[] { 0x7E, 0xA, 0x0, 0x0, 0xB6, 0xB5, 0x7E }); - - SerialDevice.Close(); - SerialDevice.Dispose(); - } - catch { } - } - - protected override void Dispose(bool disposing) - { - if (Disposed) - { - return; - } - - if (disposing) - { - CloseVolume(); - - base.Dispose(disposing); - } - } - - internal void OpenVolume(bool WriteAccess) - { - OpenWithWriteAccess = false; - - if (IsVolumeOpen()) - { - throw new Exception("Volume already opened"); - } - - if (WriteAccess) - { - // Unmounting the volume does not have the desired effect. - // It does not unmount the mountpoints on the phone. - // So the sectors of the filesystems of EFIESP, Data, etc cannot be written. - // Unmounting the mounting points would alter the NTFS structure, which is also an undesired effect. - // Restoring partitions with file-systems can better be done using Flash mode! - - // Open volume - hVolume = NativeMethods.CreateFile( - PhysicalDrive, - NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, - NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, - IntPtr.Zero, - NativeMethods.OPEN_EXISTING, - NativeMethods.FILE_FLAG_WRITE_THROUGH | NativeMethods.FILE_FLAG_NO_BUFFERING, // !!! - IntPtr.Zero); - } - else - { - hVolume = NativeMethods.CreateFile( - // @"\\.\" + Drive, - PhysicalDrive, - NativeMethods.GENERIC_READ, - NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, - IntPtr.Zero, - NativeMethods.OPEN_EXISTING, - 0, - IntPtr.Zero); - } - - if ((int)hVolume == -1) - { - throw new Exception(Marshal.GetLastWin32Error().ToString()); - } - - OpenWithWriteAccess = WriteAccess; - } - - internal bool IsVolumeOpen() - { - return (int)hVolume != -1; - } - - internal void CloseVolume() - { - if ((int)hDrive != -1) - { - NativeMethods.CloseHandle(hDrive); // This reloads the logical drive!! - hDrive = new IntPtr(-1); - } - - if ((int)hVolume != -1) - { - NativeMethods.CloseHandle(hVolume); - hVolume = new IntPtr(-1); - } - } - - internal void SetSectorPosition(UInt64 Sector) - { - if (!IsVolumeOpen()) - { - throw new Exception("Volume is not opened"); - } - - int High = (int)(Sector >> (32 - 9)); - NativeMethods.SetFilePointer(hVolume, (int)(Sector << 9), ref High, EMoveMethod.Begin); - } - - internal void WriteSector(byte[] buffer) - { - if (!IsVolumeOpen()) - { - throw new Exception("Volume is not opened"); - } - - if (!OpenWithWriteAccess) - { - throw new Exception("Volume is not opened with Write Acces"); - } - - bool result = NativeMethods.WriteFile(hVolume, buffer, 512, out uint count, IntPtr.Zero); - if (!result) - { - throw new Exception("Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); - } - } - - internal void ReadSector(byte[] buffer) - { - if (!IsVolumeOpen()) - { - throw new Exception("Volume is not opened"); - } - - bool result = NativeMethods.ReadFile(hVolume, buffer, 512, out uint count, IntPtr.Zero); - if (!result) - { - throw new Exception("Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); - } - } - - internal void ReadSectors(byte[] buffer, out uint ActualSectorsRead, uint SizeInSectors = uint.MaxValue) - { - if (!IsVolumeOpen()) - { - throw new Exception("Volume is not opened"); - } - - bool Result = NativeMethods.ReadFile(hVolume, buffer, (SizeInSectors * 0x200) < buffer.Length ? (SizeInSectors * 0x200) : (uint)buffer.Length, out uint count, IntPtr.Zero); - ActualSectorsRead = count / 0x200; - if (!Result) - { - throw new Exception("Failed to read sectors. Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); - } - } - - internal byte[] ReadSectors(UInt64 StartSector, UInt64 SectorCount) - { - byte[] Result = new byte[SectorCount * 0x200]; - - bool VolumeWasOpen = IsVolumeOpen(); - if (!VolumeWasOpen) - { - OpenVolume(false); - } - - SetSectorPosition(StartSector); - ReadSectors(Result, out uint SectorsRead); - - if (!VolumeWasOpen) - { - CloseVolume(); - } - - if (Result == null) - { - throw new Exception("Failed to read from phone"); - } - - return Result; - } - - internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path) - { - DumpSectors(StartSector, SectorCount, Path, null, null, null); - } - - internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path, Action ProgressUpdateCallback) - { - DumpSectors(StartSector, SectorCount, Path, null, ProgressUpdateCallback, null); - } - - internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path, ProgressUpdater UpdaterPerSector) - { - DumpSectors(StartSector, SectorCount, Path, null, null, UpdaterPerSector); - } - - internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, Stream OutputStream) - { - DumpSectors(StartSector, SectorCount, null, OutputStream, null, null); - } - - internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, Stream OutputStream, Action ProgressUpdateCallback) - { - DumpSectors(StartSector, SectorCount, null, OutputStream, ProgressUpdateCallback, null); - } - - internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, Stream OutputStream, ProgressUpdater UpdaterPerSector) - { - DumpSectors(StartSector, SectorCount, null, OutputStream, null, UpdaterPerSector); - } - - private void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path, Stream OutputStream, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) - { - bool VolumeWasOpen = IsVolumeOpen(); - if (!VolumeWasOpen) - { - OpenVolume(false); - } - - SetSectorPosition(StartSector); - ProgressUpdater Progress = UpdaterPerSector; - if ((Progress == null) && (ProgressUpdateCallback != null)) - { - Progress = new ProgressUpdater(SectorCount, ProgressUpdateCallback); - } - - byte[] Buffer = SectorCount >= 0x80 ? (new byte[0x10000]) : (new byte[SectorCount * 0x200]); - - Stream Stream = Path == null ? OutputStream : File.Open(Path, FileMode.Create); - using (BinaryWriter Writer = new(Stream)) - { - for (UInt64 i = 0; i < SectorCount; i += 0x80) - { - // TODO: Reading sectors and writing to compressed stream should be on different threads. - // Backup of 3 partitions without compression takes about 40 minutes. - // Backup of same partitions with compression takes about 70 minutes. - // Separation reading and compression could potentially speed up a lot. - // BinaryWriter doesnt support async. - // Calling async directly on the EntryStream of a Ziparchive blocks. - - ReadSectors(Buffer, out uint ActualSectorsRead, (SectorCount - i) >= 0x80 ? 0x80 : (uint)(SectorCount - i)); - Writer.Write(Buffer, 0, (int)ActualSectorsRead * 0x200); - Progress?.IncreaseProgress(ActualSectorsRead); - } - Stream.Flush(); - } - - if (!VolumeWasOpen) - { - CloseVolume(); - } - } - - internal void BackupPartition(string PartitionName, string Path) - { - BackupPartition(PartitionName, Path, null, null, null); - } - - internal void BackupPartition(string PartitionName, string Path, Action ProgressUpdateCallback) - { - BackupPartition(PartitionName, Path, null, ProgressUpdateCallback, null); - } - - internal void BackupPartition(string PartitionName, string Path, ProgressUpdater UpdaterPerSector) - { - BackupPartition(PartitionName, Path, null, null, UpdaterPerSector); - } - - internal void BackupPartition(string PartitionName, Stream OutputStream) - { - BackupPartition(PartitionName, null, OutputStream, null, null); - } - - internal void BackupPartition(string PartitionName, Stream OutputStream, Action ProgressUpdateCallback) - { - BackupPartition(PartitionName, null, OutputStream, ProgressUpdateCallback, null); - } - - internal void BackupPartition(string PartitionName, Stream OutputStream, ProgressUpdater UpdaterPerSector) - { - BackupPartition(PartitionName, null, OutputStream, null, UpdaterPerSector); - } - - private void BackupPartition(string PartitionName, string Path, Stream OutputStream, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) - { - bool VolumeWasOpen = IsVolumeOpen(); - if (!VolumeWasOpen) - { - OpenVolume(false); - } - - SetSectorPosition(1); - byte[] GPTBuffer = ReadSectors(1, 33); - GPT GPT = new(GPTBuffer); - Partition Partition = GPT.Partitions.First((p) => p.Name == PartitionName); - - DumpSectors(Partition.FirstSector, Partition.LastSector - Partition.FirstSector + 1, Path, OutputStream, ProgressUpdateCallback, UpdaterPerSector); - - if (!VolumeWasOpen) - { - CloseVolume(); - } - } - - internal void WriteSectors(UInt64 StartSector, byte[] Buffer) - { - bool Result = true; - - bool VolumeWasOpen = IsVolumeOpen(); - if (!VolumeWasOpen) - { - OpenVolume(true); - } - - SetSectorPosition(StartSector); - - Result = NativeMethods.WriteFile(hVolume, Buffer, (uint)Buffer.Length, out uint count, IntPtr.Zero); - - if (!VolumeWasOpen) - { - CloseVolume(); - } - - if (!Result) - { - throw new Exception("Failed to write sectors."); - } - } - - internal void WriteSectors(byte[] Buffer, uint Length = uint.MaxValue) - { - if (!IsVolumeOpen()) - { - throw new Exception("Volume is not opened"); - } - - bool Result = NativeMethods.WriteFile(hVolume, Buffer, Length < Buffer.Length ? Length : (uint)Buffer.Length, out uint count, IntPtr.Zero); - if (!Result) - { - throw new Exception("Failed to write sectors. Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); - } - } - - internal void WriteSectors(UInt64 StartSector, string Path) - { - WriteSectors(StartSector, Path, null, null); - } - - internal void WriteSectors(UInt64 StartSector, string Path, Action ProgressUpdateCallback) - { - WriteSectors(StartSector, Path, ProgressUpdateCallback, null); - } - - internal void WriteSectors(UInt64 StartSector, string Path, ProgressUpdater UpdaterPerSector) - { - WriteSectors(StartSector, Path, null, UpdaterPerSector); - } - - private void WriteSectors(UInt64 StartSector, string Path, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) - { - bool VolumeWasOpen = IsVolumeOpen(); - if (!VolumeWasOpen) - { - OpenVolume(true); - } - - SetSectorPosition(StartSector); - - byte[] Buffer; - - using (BinaryReader Reader = new(File.Open(Path, FileMode.Open))) - { - ProgressUpdater Progress = UpdaterPerSector; - if ((Progress == null) && (ProgressUpdateCallback != null)) - { - Progress = new ProgressUpdater((UInt64)(Reader.BaseStream.Length / 0x200), ProgressUpdateCallback); - } - - Buffer = Reader.BaseStream.Length >= 0x10000 ? (new byte[0x10000]) : (new byte[Reader.BaseStream.Length]); - - int Count; - for (UInt64 i = 0; i < (UInt64)(Reader.BaseStream.Length / 0x200); i += 0x80) - { - Count = Reader.Read(Buffer, 0, Buffer.Length); - - WriteSectors(Buffer, (uint)Count); - - Progress?.IncreaseProgress((ulong)Count / 0x200); - } - } - - if (!VolumeWasOpen) - { - CloseVolume(); - } - } - - internal void RestorePartition(string Path, string PartitionName) - { - RestorePartition(Path, PartitionName, null, null); - } - - internal void RestorePartition(string Path, string PartitionName, Action ProgressUpdateCallback) - { - RestorePartition(Path, PartitionName, ProgressUpdateCallback, null); - } - - internal void RestorePartition(string Path, string PartitionName, ProgressUpdater UpdaterPerSector) - { - RestorePartition(Path, PartitionName, null, UpdaterPerSector); - } - - private void RestorePartition(string Path, string PartitionName, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) - { - bool VolumeWasOpen = IsVolumeOpen(); - if (!VolumeWasOpen) - { - OpenVolume(true); - } - - SetSectorPosition(1); - byte[] GPTBuffer = ReadSectors(1, 33); - GPT GPT = new(GPTBuffer); - Partition Partition = GPT.Partitions.First((p) => p.Name == PartitionName); - ulong PartitionSize = (Partition.LastSector - Partition.FirstSector + 1) * 0x200; - ulong FileSize = (ulong)new FileInfo(Path).Length; - if (FileSize > PartitionSize) - { - throw new InvalidOperationException("Partition can not be restored, because its size is too big!"); - } - - WriteSectors(Partition.FirstSector, Path, ProgressUpdateCallback); - - if (!VolumeWasOpen) - { - CloseVolume(); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Management; +using System.Runtime.InteropServices; + +namespace WPinternals +{ + internal class MassStorage : NokiaPhoneModel + { + internal string Drive = null; + internal string PhysicalDrive = null; + internal string VolumeLabel = null; + internal IntPtr hVolume = (IntPtr)(-1); + internal IntPtr hDrive = (IntPtr)(-1); + private bool OpenWithWriteAccess; + + private string Serial; + + internal MassStorage(string DevicePath) : base(DevicePath) + { + try + { + foreach (ManagementObject logical in new ManagementObjectSearcher("select * from Win32_LogicalDisk").Get()) + { + System.Diagnostics.Debug.Print(logical["Name"].ToString()); + + string Label = ""; + foreach (ManagementObject partition in logical.GetRelated("Win32_DiskPartition")) + { + foreach (ManagementObject drive in partition.GetRelated("Win32_DiskDrive")) + { + if (drive["PNPDeviceID"].ToString().Contains("VEN_QUALCOMM&PROD_MMC_STORAGE", StringComparison.CurrentCulture) || + drive["PNPDeviceID"].ToString().Contains("VEN_MSFT&PROD_PHONE_MMC_STOR", StringComparison.CurrentCulture)) + { + Label = logical["VolumeName"] == null ? "" : logical["VolumeName"].ToString(); + if ((Drive == null) || string.Equals(Label, "MainOS", StringComparison.CurrentCultureIgnoreCase)) // Always prefer the MainOS drive-mapping + { + Drive = logical["Name"].ToString(); + PhysicalDrive = drive["DeviceID"].ToString(); + VolumeLabel = Label; + } + if (string.Equals(Label, "MainOS", StringComparison.CurrentCultureIgnoreCase)) + { + break; + } + } + } + if (string.Equals(Label, "MainOS", StringComparison.CurrentCultureIgnoreCase)) + { + break; + } + } + } + } + catch { } + } + + internal void AttachQualcommSerial(string DevicePath) + { + try + { + QualcommSerial SerialDevice = new(DevicePath); + SerialDevice.Close(); + SerialDevice.Dispose(); + + Serial = DevicePath; + } + catch (Exception ex) + { + LogFile.Log(ex.Message); + } + } + + internal bool DoesDeviceSupportReboot() + { + return Serial != null; + } + + internal void Reboot() + { + if (Serial == null) + { + return; + } + + try + { + QualcommSerial SerialDevice = new(Serial); + + SerialDevice.EncodeCommands = false; + + // This will succeed on new models + SerialDevice.SendData(new byte[] { 0x7, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0 }); + + // This will succeed on old models + SerialDevice.SendData(new byte[] { 0x7E, 0xA, 0x0, 0x0, 0xB6, 0xB5, 0x7E }); + + SerialDevice.Close(); + SerialDevice.Dispose(); + } + catch { } + } + + protected override void Dispose(bool disposing) + { + if (Disposed) + { + return; + } + + if (disposing) + { + CloseVolume(); + + base.Dispose(disposing); + } + } + + internal void OpenVolume(bool WriteAccess) + { + OpenWithWriteAccess = false; + + if (IsVolumeOpen()) + { + throw new Exception("Volume already opened"); + } + + if (WriteAccess) + { + // Unmounting the volume does not have the desired effect. + // It does not unmount the mountpoints on the phone. + // So the sectors of the filesystems of EFIESP, Data, etc cannot be written. + // Unmounting the mounting points would alter the NTFS structure, which is also an undesired effect. + // Restoring partitions with file-systems can better be done using Flash mode! + + // Open volume + hVolume = NativeMethods.CreateFile( + PhysicalDrive, + NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, + NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, + IntPtr.Zero, + NativeMethods.OPEN_EXISTING, + NativeMethods.FILE_FLAG_WRITE_THROUGH | NativeMethods.FILE_FLAG_NO_BUFFERING, // !!! + IntPtr.Zero); + } + else + { + hVolume = NativeMethods.CreateFile( + // @"\\.\" + Drive, + PhysicalDrive, + NativeMethods.GENERIC_READ, + NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, + IntPtr.Zero, + NativeMethods.OPEN_EXISTING, + 0, + IntPtr.Zero); + } + + if ((int)hVolume == -1) + { + throw new Exception(Marshal.GetLastWin32Error().ToString()); + } + + OpenWithWriteAccess = WriteAccess; + } + + internal bool IsVolumeOpen() + { + return (int)hVolume != -1; + } + + internal void CloseVolume() + { + if ((int)hDrive != -1) + { + NativeMethods.CloseHandle(hDrive); // This reloads the logical drive!! + hDrive = new IntPtr(-1); + } + + if ((int)hVolume != -1) + { + NativeMethods.CloseHandle(hVolume); + hVolume = new IntPtr(-1); + } + } + + internal void SetSectorPosition(UInt64 Sector) + { + if (!IsVolumeOpen()) + { + throw new Exception("Volume is not opened"); + } + + int High = (int)(Sector >> (32 - 9)); + NativeMethods.SetFilePointer(hVolume, (int)(Sector << 9), ref High, EMoveMethod.Begin); + } + + internal void WriteSector(byte[] buffer) + { + if (!IsVolumeOpen()) + { + throw new Exception("Volume is not opened"); + } + + if (!OpenWithWriteAccess) + { + throw new Exception("Volume is not opened with Write Acces"); + } + + bool result = NativeMethods.WriteFile(hVolume, buffer, 512, out uint count, IntPtr.Zero); + if (!result) + { + throw new Exception("Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); + } + } + + internal void ReadSector(byte[] buffer) + { + if (!IsVolumeOpen()) + { + throw new Exception("Volume is not opened"); + } + + bool result = NativeMethods.ReadFile(hVolume, buffer, 512, out uint count, IntPtr.Zero); + if (!result) + { + throw new Exception("Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); + } + } + + internal void ReadSectors(byte[] buffer, out uint ActualSectorsRead, uint SizeInSectors = uint.MaxValue) + { + if (!IsVolumeOpen()) + { + throw new Exception("Volume is not opened"); + } + + bool Result = NativeMethods.ReadFile(hVolume, buffer, (SizeInSectors * 0x200) < buffer.Length ? (SizeInSectors * 0x200) : (uint)buffer.Length, out uint count, IntPtr.Zero); + ActualSectorsRead = count / 0x200; + if (!Result) + { + throw new Exception("Failed to read sectors. Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); + } + } + + internal byte[] ReadSectors(UInt64 StartSector, UInt64 SectorCount) + { + byte[] Result = new byte[SectorCount * 0x200]; + + bool VolumeWasOpen = IsVolumeOpen(); + if (!VolumeWasOpen) + { + OpenVolume(false); + } + + SetSectorPosition(StartSector); + ReadSectors(Result, out uint SectorsRead); + + if (!VolumeWasOpen) + { + CloseVolume(); + } + + if (Result == null) + { + throw new Exception("Failed to read from phone"); + } + + return Result; + } + + internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path) + { + DumpSectors(StartSector, SectorCount, Path, null, null, null); + } + + internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path, Action ProgressUpdateCallback) + { + DumpSectors(StartSector, SectorCount, Path, null, ProgressUpdateCallback, null); + } + + internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path, ProgressUpdater UpdaterPerSector) + { + DumpSectors(StartSector, SectorCount, Path, null, null, UpdaterPerSector); + } + + internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, Stream OutputStream) + { + DumpSectors(StartSector, SectorCount, null, OutputStream, null, null); + } + + internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, Stream OutputStream, Action ProgressUpdateCallback) + { + DumpSectors(StartSector, SectorCount, null, OutputStream, ProgressUpdateCallback, null); + } + + internal void DumpSectors(UInt64 StartSector, UInt64 SectorCount, Stream OutputStream, ProgressUpdater UpdaterPerSector) + { + DumpSectors(StartSector, SectorCount, null, OutputStream, null, UpdaterPerSector); + } + + private void DumpSectors(UInt64 StartSector, UInt64 SectorCount, string Path, Stream OutputStream, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) + { + bool VolumeWasOpen = IsVolumeOpen(); + if (!VolumeWasOpen) + { + OpenVolume(false); + } + + SetSectorPosition(StartSector); + ProgressUpdater Progress = UpdaterPerSector; + if ((Progress == null) && (ProgressUpdateCallback != null)) + { + Progress = new ProgressUpdater(SectorCount, ProgressUpdateCallback); + } + + byte[] Buffer = SectorCount >= 0x80 ? (new byte[0x10000]) : (new byte[SectorCount * 0x200]); + + Stream Stream = Path == null ? OutputStream : File.Open(Path, FileMode.Create); + using (BinaryWriter Writer = new(Stream)) + { + for (UInt64 i = 0; i < SectorCount; i += 0x80) + { + // TODO: Reading sectors and writing to compressed stream should be on different threads. + // Backup of 3 partitions without compression takes about 40 minutes. + // Backup of same partitions with compression takes about 70 minutes. + // Separation reading and compression could potentially speed up a lot. + // BinaryWriter doesnt support async. + // Calling async directly on the EntryStream of a Ziparchive blocks. + + ReadSectors(Buffer, out uint ActualSectorsRead, (SectorCount - i) >= 0x80 ? 0x80 : (uint)(SectorCount - i)); + Writer.Write(Buffer, 0, (int)ActualSectorsRead * 0x200); + Progress?.IncreaseProgress(ActualSectorsRead); + } + Stream.Flush(); + } + + if (!VolumeWasOpen) + { + CloseVolume(); + } + } + + internal void BackupPartition(string PartitionName, string Path) + { + BackupPartition(PartitionName, Path, null, null, null); + } + + internal void BackupPartition(string PartitionName, string Path, Action ProgressUpdateCallback) + { + BackupPartition(PartitionName, Path, null, ProgressUpdateCallback, null); + } + + internal void BackupPartition(string PartitionName, string Path, ProgressUpdater UpdaterPerSector) + { + BackupPartition(PartitionName, Path, null, null, UpdaterPerSector); + } + + internal void BackupPartition(string PartitionName, Stream OutputStream) + { + BackupPartition(PartitionName, null, OutputStream, null, null); + } + + internal void BackupPartition(string PartitionName, Stream OutputStream, Action ProgressUpdateCallback) + { + BackupPartition(PartitionName, null, OutputStream, ProgressUpdateCallback, null); + } + + internal void BackupPartition(string PartitionName, Stream OutputStream, ProgressUpdater UpdaterPerSector) + { + BackupPartition(PartitionName, null, OutputStream, null, UpdaterPerSector); + } + + private void BackupPartition(string PartitionName, string Path, Stream OutputStream, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) + { + bool VolumeWasOpen = IsVolumeOpen(); + if (!VolumeWasOpen) + { + OpenVolume(false); + } + + SetSectorPosition(1); + byte[] GPTBuffer = ReadSectors(1, 33); + GPT GPT = new(GPTBuffer); + Partition Partition = GPT.Partitions.First((p) => p.Name == PartitionName); + + DumpSectors(Partition.FirstSector, Partition.LastSector - Partition.FirstSector + 1, Path, OutputStream, ProgressUpdateCallback, UpdaterPerSector); + + if (!VolumeWasOpen) + { + CloseVolume(); + } + } + + internal void WriteSectors(UInt64 StartSector, byte[] Buffer) + { + bool Result = true; + + bool VolumeWasOpen = IsVolumeOpen(); + if (!VolumeWasOpen) + { + OpenVolume(true); + } + + SetSectorPosition(StartSector); + + Result = NativeMethods.WriteFile(hVolume, Buffer, (uint)Buffer.Length, out uint count, IntPtr.Zero); + + if (!VolumeWasOpen) + { + CloseVolume(); + } + + if (!Result) + { + throw new Exception("Failed to write sectors."); + } + } + + internal void WriteSectors(byte[] Buffer, uint Length = uint.MaxValue) + { + if (!IsVolumeOpen()) + { + throw new Exception("Volume is not opened"); + } + + bool Result = NativeMethods.WriteFile(hVolume, Buffer, Length < Buffer.Length ? Length : (uint)Buffer.Length, out uint count, IntPtr.Zero); + if (!Result) + { + throw new Exception("Failed to write sectors. Exception: 0x" + Marshal.GetLastWin32Error().ToString("X8")); + } + } + + internal void WriteSectors(UInt64 StartSector, string Path) + { + WriteSectors(StartSector, Path, null, null); + } + + internal void WriteSectors(UInt64 StartSector, string Path, Action ProgressUpdateCallback) + { + WriteSectors(StartSector, Path, ProgressUpdateCallback, null); + } + + internal void WriteSectors(UInt64 StartSector, string Path, ProgressUpdater UpdaterPerSector) + { + WriteSectors(StartSector, Path, null, UpdaterPerSector); + } + + private void WriteSectors(UInt64 StartSector, string Path, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) + { + bool VolumeWasOpen = IsVolumeOpen(); + if (!VolumeWasOpen) + { + OpenVolume(true); + } + + SetSectorPosition(StartSector); + + byte[] Buffer; + + using (BinaryReader Reader = new(File.Open(Path, FileMode.Open))) + { + ProgressUpdater Progress = UpdaterPerSector; + if ((Progress == null) && (ProgressUpdateCallback != null)) + { + Progress = new ProgressUpdater((UInt64)(Reader.BaseStream.Length / 0x200), ProgressUpdateCallback); + } + + Buffer = Reader.BaseStream.Length >= 0x10000 ? (new byte[0x10000]) : (new byte[Reader.BaseStream.Length]); + + int Count; + for (UInt64 i = 0; i < (UInt64)(Reader.BaseStream.Length / 0x200); i += 0x80) + { + Count = Reader.Read(Buffer, 0, Buffer.Length); + + WriteSectors(Buffer, (uint)Count); + + Progress?.IncreaseProgress((ulong)Count / 0x200); + } + } + + if (!VolumeWasOpen) + { + CloseVolume(); + } + } + + internal void RestorePartition(string Path, string PartitionName) + { + RestorePartition(Path, PartitionName, null, null); + } + + internal void RestorePartition(string Path, string PartitionName, Action ProgressUpdateCallback) + { + RestorePartition(Path, PartitionName, ProgressUpdateCallback, null); + } + + internal void RestorePartition(string Path, string PartitionName, ProgressUpdater UpdaterPerSector) + { + RestorePartition(Path, PartitionName, null, UpdaterPerSector); + } + + private void RestorePartition(string Path, string PartitionName, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) + { + bool VolumeWasOpen = IsVolumeOpen(); + if (!VolumeWasOpen) + { + OpenVolume(true); + } + + SetSectorPosition(1); + byte[] GPTBuffer = ReadSectors(1, 33); + GPT GPT = new(GPTBuffer); + Partition Partition = GPT.Partitions.First((p) => p.Name == PartitionName); + ulong PartitionSize = (Partition.LastSector - Partition.FirstSector + 1) * 0x200; + ulong FileSize = (ulong)new FileInfo(Path).Length; + if (FileSize > PartitionSize) + { + throw new InvalidOperationException("Partition can not be restored, because its size is too big!"); + } + + WriteSectors(Partition.FirstSector, Path, ProgressUpdateCallback); + + if (!VolumeWasOpen) + { + CloseVolume(); + } + } + } +} diff --git a/Models/NativeMethods.cs b/WPinternals/Models/NativeMethods.cs similarity index 97% rename from Models/NativeMethods.cs rename to WPinternals/Models/NativeMethods.cs index 4250d75..79f1d6c 100644 --- a/Models/NativeMethods.cs +++ b/WPinternals/Models/NativeMethods.cs @@ -1,302 +1,302 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using Microsoft.Win32.SafeHandles; -using System; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Security; - -namespace WPinternals -{ - [Flags] - internal enum TokenAccessLevels - { - AssignPrimary = 0x00000001, - Duplicate = 0x00000002, - Impersonate = 0x00000004, - Query = 0x00000008, - QuerySource = 0x00000010, - AdjustPrivileges = 0x00000020, - AdjustGroups = 0x00000040, - AdjustDefault = 0x00000080, - AdjustSessionId = 0x00000100, - - Read = 0x00020000 | Query, - - Write = 0x00020000 | AdjustPrivileges | AdjustGroups | AdjustDefault, - - AllAccess = 0x000F0000 | - AssignPrimary | - Duplicate | - Impersonate | - Query | - QuerySource | - AdjustPrivileges | - AdjustGroups | - AdjustDefault | - AdjustSessionId, - - MaximumAllowed = 0x02000000 - } - - internal enum SecurityImpersonationLevel - { - Anonymous = 0, - Identification = 1, - Impersonation = 2, - Delegation = 3, - } - - internal enum TokenType - { - Primary = 1, - Impersonation = 2, - } - - internal enum EMoveMethod : uint - { - Begin = 0, - Current = 1, - End = 2 - } - - internal sealed class NativeMethods - { - internal const uint SE_PRIVILEGE_DISABLED = 0x00000000; - internal const uint SE_PRIVILEGE_ENABLED = 0x00000002; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct LUID - { - internal uint LowPart; - internal uint HighPart; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct LUID_AND_ATTRIBUTES - { - internal LUID Luid; - internal uint Attributes; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct TOKEN_PRIVILEGE - { - internal uint PrivilegeCount; - internal LUID_AND_ATTRIBUTES Privilege; - } - - internal const string ADVAPI32 = "advapi32.dll"; - internal const string KERNEL32 = "kernel32.dll"; - - internal const int ERROR_SUCCESS = 0x0; - internal const int ERROR_ACCESS_DENIED = 0x5; - internal const int ERROR_NOT_ENOUGH_MEMORY = 0x8; - internal const int ERROR_NO_TOKEN = 0x3f0; - internal const int ERROR_NOT_ALL_ASSIGNED = 0x514; - internal const int ERROR_NO_SUCH_PRIVILEGE = 0x521; - internal const int ERROR_CANT_OPEN_ANONYMOUS = 0x543; - - [DllImport( - KERNEL32, - SetLastError = true)] - internal static extern bool CloseHandle(IntPtr handle); - - [DllImport( - ADVAPI32, - CharSet = CharSet.Unicode, - SetLastError = true)] - internal static extern bool AdjustTokenPrivileges( - [In] SafeTokenHandle TokenHandle, - [In] bool DisableAllPrivileges, - [In] ref TOKEN_PRIVILEGE NewState, - [In] uint BufferLength, - [In, Out] ref TOKEN_PRIVILEGE PreviousState, - [In, Out] ref uint ReturnLength); - - [DllImport( - ADVAPI32, - CharSet = CharSet.Auto, - SetLastError = true)] - internal static extern - bool RevertToSelf(); - - [DllImport( - ADVAPI32, - EntryPoint = "LookupPrivilegeValueW", - CharSet = CharSet.Auto, - SetLastError = true)] - internal static extern - bool LookupPrivilegeValue( - [In] string lpSystemName, - [In] string lpName, - [In, Out] ref LUID Luid); - - [DllImport( - KERNEL32, - CharSet = CharSet.Auto, - SetLastError = true)] - internal static extern - IntPtr GetCurrentProcess(); - - [DllImport( - KERNEL32, - CharSet = CharSet.Auto, - SetLastError = true)] - internal static extern - IntPtr GetCurrentThread(); - - [DllImport( - ADVAPI32, - CharSet = CharSet.Unicode, - SetLastError = true)] - internal static extern - bool OpenProcessToken( - [In] IntPtr ProcessToken, - [In] TokenAccessLevels DesiredAccess, - [In, Out] ref SafeTokenHandle TokenHandle); - - [DllImport - (ADVAPI32, - CharSet = CharSet.Unicode, - SetLastError = true)] - internal static extern - bool OpenThreadToken( - [In] IntPtr ThreadToken, - [In] TokenAccessLevels DesiredAccess, - [In] bool OpenAsSelf, - [In, Out] ref SafeTokenHandle TokenHandle); - - [DllImport - (ADVAPI32, - CharSet = CharSet.Unicode, - SetLastError = true)] - internal static extern - bool DuplicateTokenEx( - [In] SafeTokenHandle ExistingToken, - [In] TokenAccessLevels DesiredAccess, - [In] IntPtr TokenAttributes, - [In] SecurityImpersonationLevel ImpersonationLevel, - [In] TokenType TokenType, - [In, Out] ref SafeTokenHandle NewToken); - - [DllImport - (ADVAPI32, - CharSet = CharSet.Unicode, - SetLastError = true)] - internal static extern - bool SetThreadToken( - [In] IntPtr Thread, - [In] SafeTokenHandle Token); - - internal const uint FILE_SHARE_READ = 0x00000001; - internal const uint FILE_SHARE_WRITE = 0x00000002; - internal const uint FILE_SHARE_DELETE = 0x00000004; - internal const uint OPEN_EXISTING = 3; - - internal const uint GENERIC_READ = 0x80000000; - internal const uint GENERIC_WRITE = 0x40000000; - - internal const uint FILE_FLAG_WRITE_THROUGH = 0x80000000; - internal const uint FILE_FLAG_NO_BUFFERING = 0x20000000; - internal const uint FILE_READ_ATTRIBUTES = 0x0080; - internal const uint FILE_WRITE_ATTRIBUTES = 0x0100; - internal const uint ERROR_INSUFFICIENT_BUFFER = 122; - internal const uint FILE_BEGIN = 0; - internal const uint FSCTL_LOCK_VOLUME = 0x00090018; - internal const uint FSCTL_DISMOUNT_VOLUME = 0x00090020; - internal const uint FSCTL_UNLOCK_VOLUME = 0x00090022; - internal const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808; - internal const uint IOCTL_STORAGE_LOAD_MEDIA = 0x2D480C; - - internal const Int32 INVALID_HANDLE_VALUE = -1; - internal const Int32 FILE_ATTRIBUTE_NORMAL = 1; - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped); - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); - - [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] - internal static extern uint SetFilePointer( - [In] IntPtr hFile, - [In] int lDistanceToMove, - [In, Out] ref int lpDistanceToMoveHigh, - [In] EMoveMethod dwMoveMethod); - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern IntPtr CreateFile( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr lpSecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)] - public static extern bool DeviceIoControl( - IntPtr hDevice, - uint IoControlCode, - [In] object InBuffer, - uint nInBufferSize, - [Out] object OutBuffer, - uint nOutBufferSize, - ref uint pBytesReturned, - IntPtr Overlapped - ); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FlushFileBuffers(IntPtr hFile); - - static NativeMethods() - { - } - } - - internal sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid - { - private SafeTokenHandle() : base(true) { } - - // 0 is an Invalid Handle - internal SafeTokenHandle(IntPtr handle) - : base(true) - { - SetHandle(handle); - } - - internal static SafeTokenHandle InvalidHandle - { - get { return new SafeTokenHandle(IntPtr.Zero); } - } - - [DllImport(NativeMethods.KERNEL32, SetLastError = true), - SuppressUnmanagedCodeSecurity] - private static extern bool CloseHandle(IntPtr handle); - - override protected bool ReleaseHandle() - { - return CloseHandle(handle); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32.SafeHandles; +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; + +namespace WPinternals +{ + [Flags] + internal enum TokenAccessLevels + { + AssignPrimary = 0x00000001, + Duplicate = 0x00000002, + Impersonate = 0x00000004, + Query = 0x00000008, + QuerySource = 0x00000010, + AdjustPrivileges = 0x00000020, + AdjustGroups = 0x00000040, + AdjustDefault = 0x00000080, + AdjustSessionId = 0x00000100, + + Read = 0x00020000 | Query, + + Write = 0x00020000 | AdjustPrivileges | AdjustGroups | AdjustDefault, + + AllAccess = 0x000F0000 | + AssignPrimary | + Duplicate | + Impersonate | + Query | + QuerySource | + AdjustPrivileges | + AdjustGroups | + AdjustDefault | + AdjustSessionId, + + MaximumAllowed = 0x02000000 + } + + internal enum SecurityImpersonationLevel + { + Anonymous = 0, + Identification = 1, + Impersonation = 2, + Delegation = 3, + } + + internal enum TokenType + { + Primary = 1, + Impersonation = 2, + } + + internal enum EMoveMethod : uint + { + Begin = 0, + Current = 1, + End = 2 + } + + internal sealed class NativeMethods + { + internal const uint SE_PRIVILEGE_DISABLED = 0x00000000; + internal const uint SE_PRIVILEGE_ENABLED = 0x00000002; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct LUID + { + internal uint LowPart; + internal uint HighPart; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct LUID_AND_ATTRIBUTES + { + internal LUID Luid; + internal uint Attributes; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct TOKEN_PRIVILEGE + { + internal uint PrivilegeCount; + internal LUID_AND_ATTRIBUTES Privilege; + } + + internal const string ADVAPI32 = "advapi32.dll"; + internal const string KERNEL32 = "kernel32.dll"; + + internal const int ERROR_SUCCESS = 0x0; + internal const int ERROR_ACCESS_DENIED = 0x5; + internal const int ERROR_NOT_ENOUGH_MEMORY = 0x8; + internal const int ERROR_NO_TOKEN = 0x3f0; + internal const int ERROR_NOT_ALL_ASSIGNED = 0x514; + internal const int ERROR_NO_SUCH_PRIVILEGE = 0x521; + internal const int ERROR_CANT_OPEN_ANONYMOUS = 0x543; + + [DllImport( + KERNEL32, + SetLastError = true)] + internal static extern bool CloseHandle(IntPtr handle); + + [DllImport( + ADVAPI32, + CharSet = CharSet.Unicode, + SetLastError = true)] + internal static extern bool AdjustTokenPrivileges( + [In] SafeTokenHandle TokenHandle, + [In] bool DisableAllPrivileges, + [In] ref TOKEN_PRIVILEGE NewState, + [In] uint BufferLength, + [In, Out] ref TOKEN_PRIVILEGE PreviousState, + [In, Out] ref uint ReturnLength); + + [DllImport( + ADVAPI32, + CharSet = CharSet.Auto, + SetLastError = true)] + internal static extern + bool RevertToSelf(); + + [DllImport( + ADVAPI32, + EntryPoint = "LookupPrivilegeValueW", + CharSet = CharSet.Auto, + SetLastError = true)] + internal static extern + bool LookupPrivilegeValue( + [In] string lpSystemName, + [In] string lpName, + [In, Out] ref LUID Luid); + + [DllImport( + KERNEL32, + CharSet = CharSet.Auto, + SetLastError = true)] + internal static extern + IntPtr GetCurrentProcess(); + + [DllImport( + KERNEL32, + CharSet = CharSet.Auto, + SetLastError = true)] + internal static extern + IntPtr GetCurrentThread(); + + [DllImport( + ADVAPI32, + CharSet = CharSet.Unicode, + SetLastError = true)] + internal static extern + bool OpenProcessToken( + [In] IntPtr ProcessToken, + [In] TokenAccessLevels DesiredAccess, + [In, Out] ref SafeTokenHandle TokenHandle); + + [DllImport + (ADVAPI32, + CharSet = CharSet.Unicode, + SetLastError = true)] + internal static extern + bool OpenThreadToken( + [In] IntPtr ThreadToken, + [In] TokenAccessLevels DesiredAccess, + [In] bool OpenAsSelf, + [In, Out] ref SafeTokenHandle TokenHandle); + + [DllImport + (ADVAPI32, + CharSet = CharSet.Unicode, + SetLastError = true)] + internal static extern + bool DuplicateTokenEx( + [In] SafeTokenHandle ExistingToken, + [In] TokenAccessLevels DesiredAccess, + [In] IntPtr TokenAttributes, + [In] SecurityImpersonationLevel ImpersonationLevel, + [In] TokenType TokenType, + [In, Out] ref SafeTokenHandle NewToken); + + [DllImport + (ADVAPI32, + CharSet = CharSet.Unicode, + SetLastError = true)] + internal static extern + bool SetThreadToken( + [In] IntPtr Thread, + [In] SafeTokenHandle Token); + + internal const uint FILE_SHARE_READ = 0x00000001; + internal const uint FILE_SHARE_WRITE = 0x00000002; + internal const uint FILE_SHARE_DELETE = 0x00000004; + internal const uint OPEN_EXISTING = 3; + + internal const uint GENERIC_READ = 0x80000000; + internal const uint GENERIC_WRITE = 0x40000000; + + internal const uint FILE_FLAG_WRITE_THROUGH = 0x80000000; + internal const uint FILE_FLAG_NO_BUFFERING = 0x20000000; + internal const uint FILE_READ_ATTRIBUTES = 0x0080; + internal const uint FILE_WRITE_ATTRIBUTES = 0x0100; + internal const uint ERROR_INSUFFICIENT_BUFFER = 122; + internal const uint FILE_BEGIN = 0; + internal const uint FSCTL_LOCK_VOLUME = 0x00090018; + internal const uint FSCTL_DISMOUNT_VOLUME = 0x00090020; + internal const uint FSCTL_UNLOCK_VOLUME = 0x00090022; + internal const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808; + internal const uint IOCTL_STORAGE_LOAD_MEDIA = 0x2D480C; + + internal const Int32 INVALID_HANDLE_VALUE = -1; + internal const Int32 FILE_ATTRIBUTE_NORMAL = 1; + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); + + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern uint SetFilePointer( + [In] IntPtr hFile, + [In] int lDistanceToMove, + [In, Out] ref int lpDistanceToMoveHigh, + [In] EMoveMethod dwMoveMethod); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern IntPtr CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)] + public static extern bool DeviceIoControl( + IntPtr hDevice, + uint IoControlCode, + [In] object InBuffer, + uint nInBufferSize, + [Out] object OutBuffer, + uint nOutBufferSize, + ref uint pBytesReturned, + IntPtr Overlapped + ); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FlushFileBuffers(IntPtr hFile); + + static NativeMethods() + { + } + } + + internal sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private SafeTokenHandle() : base(true) { } + + // 0 is an Invalid Handle + internal SafeTokenHandle(IntPtr handle) + : base(true) + { + SetHandle(handle); + } + + internal static SafeTokenHandle InvalidHandle + { + get { return new SafeTokenHandle(IntPtr.Zero); } + } + + [DllImport(NativeMethods.KERNEL32, SetLastError = true), + SuppressUnmanagedCodeSecurity] + private static extern bool CloseHandle(IntPtr handle); + + override protected bool ReleaseHandle() + { + return CloseHandle(handle); + } + } +} diff --git a/Models/NokiaFlashModel.cs b/WPinternals/Models/NokiaFlashModel.cs similarity index 97% rename from Models/NokiaFlashModel.cs rename to WPinternals/Models/NokiaFlashModel.cs index 6fd324e..7f14ed0 100644 --- a/Models/NokiaFlashModel.cs +++ b/WPinternals/Models/NokiaFlashModel.cs @@ -1,1409 +1,1409 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Linq; - -namespace WPinternals -{ - internal enum FfuProtocol - { - ProtocolSyncV1 = 1, - ProtocolAsyncV1 = 2, - ProtocolSyncV2 = 4, - ProtocolAsyncV2 = 8, - ProtocolAsyncV3 = 16 - } - - [Flags] - internal enum FlashOptions : byte - { - SkipWrite = 1, - SkipHashCheck = 2, - SkipIdCheck = 4, - SkipSignatureCheck = 8 - } - - internal delegate void InterfaceChangedHandler(PhoneInterfaces NewInterface); - - internal class NokiaFlashModel : NokiaPhoneModel - { - private UefiSecurityStatusResponse _SecurityStatus = null; - private readonly PhoneInfo Info = new(); - - internal event InterfaceChangedHandler InterfaceChanged = delegate { }; - - public NokiaFlashModel(string DevicePath) : base(DevicePath) { } - - public byte[] ReadParam(string Param) - { - byte[] Request = new byte[0x0B]; - const string Header = "NOKXFR"; - - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Param), 0, Request, 7, Param.Length); - - byte[] Response = ExecuteRawMethod(Request); - if ((Response == null) || (Response.Length < 0x10)) - { - return null; - } - - byte[] Result = new byte[Response[0x10]]; - Buffer.BlockCopy(Response, 0x11, Result, 0, Response[0x10]); - return Result; - } - - public string ReadStringParam(string Param) - { - byte[] Bytes = ReadParam(Param); - if (Bytes == null) - { - return null; - } - - return System.Text.Encoding.ASCII.GetString(Bytes).Trim(new char[] { '\0' }); - } - - [Flags] - internal enum Fuse - { - SecureBoot = 1, - FfuVerify = 2, - Jtag = 4, - Shk = 8, - Simlock = 16, - ProductionDone = 32, - Rkh = 64, - PublicId = 128, - Dak = 256, - SecGen = 512, - OemId = 1024, - FastBoot = 2048, - SpdmSecMode = 4096, - RpmWdog = 8192, - Ssm = 16384 - } - - public bool? ReadFuseStatus(Fuse fuse) - { - uint? flags = ReadSecurityFlags(); - if (!flags.HasValue) - { - return null; - } - - var finalconfig = (Fuse)flags.Value; - return finalconfig.HasFlag(fuse); - } - - public uint? ReadSecurityFlags() - { - byte[] Response = ReadParam("FCS"); - if ((Response == null) || (Response.Length != 4)) - { - return null; - } - - // This value is in big endian - return (UInt32)((Response[0] << 24) | (Response[1] << 16) | (Response[2] << 8) | Response[3]); - } - - public uint? ReadCurrentChargeLevel() - { - byte[] Response = ReadParam("CS"); - if ((Response == null) || (Response.Length != 8)) - { - return null; - } - - // This value is in big endian - return (UInt32)((Response[0] << 24) | (Response[1] << 16) | (Response[2] << 8) | Response[3]) + 1; - } - - public int? ReadCurrentChargeCurrent() - { - byte[] Response = ReadParam("CS"); - if ((Response == null) || (Response.Length != 8)) - { - return null; - } - - // This value is in big endian and needs to be XOR'd with 0xFFFFFFFF - return (Int32)(((Response[4] << 24) | (Response[5] << 16) | (Response[6] << 8) | Response[7]) ^ 0xFFFFFFFF) + 1; - } - - public UefiSecurityStatusResponse ReadSecurityStatus() - { - if (_SecurityStatus != null) - { - return _SecurityStatus; - } - - byte[] Response = ReadParam("SS"); - if (Response == null) - { - return null; - } - - UefiSecurityStatusResponse Result = new(); - - Result.IsTestDevice = Response[0]; - Result.PlatformSecureBootStatus = Convert.ToBoolean(Response[1]); - Result.SecureFfuEfuseStatus = Convert.ToBoolean(Response[2]); - Result.DebugStatus = Convert.ToBoolean(Response[3]); - Result.RdcStatus = Convert.ToBoolean(Response[4]); - Result.AuthenticationStatus = Convert.ToBoolean(Response[5]); - Result.UefiSecureBootStatus = Convert.ToBoolean(Response[6]); - Result.CryptoHardwareKey = Convert.ToBoolean(Response[7]); - - _SecurityStatus = Result; - - return Result; - } - - internal bool? IsBootLoaderUnlocked() - { - UefiSecurityStatusResponse SecurityStatus = ReadSecurityStatus(); - if (SecurityStatus != null) - { - return SecurityStatus.AuthenticationStatus || SecurityStatus.RdcStatus || !SecurityStatus.SecureFfuEfuseStatus; - } - - return null; - } - - public TerminalResponse GetTerminalResponse() - { - byte[] AsskMask = new byte[0x10] { 1, 0, 16, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 }; - byte[] Request = new byte[0xAC]; - const string Header = "NOKXFT"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Request[7] = 1; - Buffer.BlockCopy(BigEndian.GetBytes(0x18, 4), 0, Request, 0x08, 4); // Subblocktype = 0x18 - Buffer.BlockCopy(BigEndian.GetBytes(0x9C, 4), 0, Request, 0x0C, 4); // Subblocklength = 0x9C - Buffer.BlockCopy(BigEndian.GetBytes(0x00, 4), 0, Request, 0x10, 4); // AsicIndex = 0x00 - Buffer.BlockCopy(AsskMask, 0, Request, 0x14, 0x10); - byte[] TerminalResponse = ExecuteRawMethod(Request); - if ((TerminalResponse?.Length > 0x20) && (BigEndian.ToUInt32(TerminalResponse, 0x14) == (TerminalResponse.Length - 0x18)) && (BitConverter.ToUInt32(TerminalResponse, 0x1C) == (TerminalResponse.Length - 0x20))) - { - // Parse Terminal Response from offset 0x18 - return Terminal.Parse(TerminalResponse, 0x18); - } - else - { - return null; - } - } - - public void SendFfuHeaderV1(byte[] FfuHeader, byte Options = 0) - { - byte[] Request = new byte[FfuHeader.Length + 0x20]; - - const string Header = "NOKXFS"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Buffer.BlockCopy(BigEndian.GetBytes(0x0001, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 - Request[0x08] = 0; // Progress = 0% - Request[0x0B] = 1; // Subblock count = 1 - Buffer.BlockCopy(BigEndian.GetBytes(0x0000000B, 4), 0, Request, 0x0C, 4); // Subblock type for header = 0x0B - Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of header + 0x0C - Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0 - Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x18, 4); // Payload length = length of header - Request[0x1C] = Options; // Header options = 0 - - Buffer.BlockCopy(FfuHeader, 0, Request, 0x20, FfuHeader.Length); - - byte[] Response = ExecuteRawMethod(Request); - if (Response == null) - { - throw new BadConnectionException(); - } - - int ResultCode = (Response[6] << 8) + Response[7]; - if (ResultCode != 0) - { - ThrowFlashError(ResultCode); - } - } - - public void SendFfuHeaderV2(UInt32 TotalHeaderLength, UInt32 OffsetForThisPart, byte[] FfuHeader, byte Options = 0) - { - byte[] Request = new byte[FfuHeader.Length + 0x3C]; - - const string Header = "NOKXFS"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 - Request[0x08] = 0; // Progress = 0% - Request[0x0B] = 1; // Subblock count = 1 - - Buffer.BlockCopy(BigEndian.GetBytes(0x00000021, 4), 0, Request, 0x0C, 4); // Subblock type for header v2 = 0x21 - Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x28, 4), 0, Request, 0x10, 4); // Subblock starts at 0x14, payload starts at 0x3C. - - Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0 - Buffer.BlockCopy(BigEndian.GetBytes(TotalHeaderLength, 4), 0, Request, 0x18, 4); // Payload length = length of header - Request[0x1C] = Options; // Header options = 0 - - Buffer.BlockCopy(BigEndian.GetBytes(OffsetForThisPart, 4), 0, Request, 0x1D, 4); - Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x21, 4); - Request[0x25] = 0; // No Erase - - Buffer.BlockCopy(FfuHeader, 0, Request, 0x3C, FfuHeader.Length); - - byte[] Response = ExecuteRawMethod(Request); - if (Response == null) - { - throw new BadConnectionException(); - } - - if (Response.Length == 4) - { - throw new WPinternalsException("Flash protocol v2 not supported", "The device reported that the Flash protocol v2 was not supported while sending the FFU header."); - } - - int ResultCode = (Response[6] << 8) + Response[7]; - if (ResultCode != 0) - { - ThrowFlashError(ResultCode); - } - } - - public void SendFfuPayloadV1(byte[] FfuChunk, int Progress = 0, byte Options = 0) - { - byte[] Request = new byte[FfuChunk.Length + 0x1C]; - - const string Header = "NOKXFS"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV1, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 - Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) - Request[0x0B] = 1; // Subblock count = 1 - - Buffer.BlockCopy(BigEndian.GetBytes(0x0000000C, 4), 0, Request, 0x0C, 4); // Subblock type for ChunkData = 0x0C - Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x08, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 - - Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk - Request[0x18] = Options; // Data options = 0 (1 = verify) - - Buffer.BlockCopy(FfuChunk, 0, Request, 0x1C, FfuChunk.Length); - - byte[] Response = ExecuteRawMethod(Request); - if (Response == null) - { - throw new BadConnectionException(); - } - - int ResultCode = (Response[6] << 8) + Response[7]; - if (ResultCode != 0) - { - ThrowFlashError(ResultCode); - } - } - - public void SendFfuPayloadV2(byte[] FfuChunk, int Progress = 0, byte Options = 0) - { - byte[] Request = new byte[FfuChunk.Length + 0x20]; - - const string Header = "NOKXFS"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol - Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) - Request[0x0B] = 1; // Subblock count = 1 - - Buffer.BlockCopy(BigEndian.GetBytes(0x0000001B, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B - Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 - - Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk - Request[0x18] = Options; // Data options = 0 (1 = verify) - - Buffer.BlockCopy(FfuChunk, 0, Request, 0x20, FfuChunk.Length); - - byte[] Response = ExecuteRawMethod(Request); - if (Response == null) - { - throw new BadConnectionException(); - } - - int ResultCode = (Response[6] << 8) + Response[7]; - if (ResultCode != 0) - { - ThrowFlashError(ResultCode); - } - } - - public void SendFfuPayloadV3(byte[] FfuChunk, UInt32 WriteDescriptorIndex, UInt32 CRC, int Progress = 0, byte Options = 0) - { - byte[] Request = new byte[FfuChunk.Length + 0x20]; - - const string Header = "NOKXFS"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolAsyncV3, 2), 0, Request, 0x06, 2); // Protocol - Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) - Request[0x0B] = 1; // Subblock count = 1 - - Buffer.BlockCopy(BigEndian.GetBytes(0x0000001D, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B - Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x2C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 - - Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk - Request[0x18] = Options; // Data options = 0 (1 = verify) - Buffer.BlockCopy(BigEndian.GetBytes(WriteDescriptorIndex, 4), 0, Request, 0x19, 4); // Payload length = length of chunk - Buffer.BlockCopy(BigEndian.GetBytes(CRC, 4), 0, Request, 0x1D, 4); // Payload length = length of chunk - - Buffer.BlockCopy(FfuChunk, 0, Request, 0x40, FfuChunk.Length); - - byte[] Response = ExecuteRawMethod(Request); - if (Response == null) - { - throw new BadConnectionException(); - } - - int ResultCode = (Response[6] << 8) + Response[7]; - if (ResultCode != 0) - { - ThrowFlashError(ResultCode); - } - } - - public void BackupPartitionToRam(string PartitionName) - { - PartitionName = PartitionName.ToUpper(); - if (new string[] { "MODEM_FSG", "MODEM_FS1", "MODEM_FS2", "SSD", "DPP" }.Any(s => s == PartitionName)) - { - byte[] Request = new byte[84]; - const string Header = "NOKXFB"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Request[0x07] = 1; // Subblock count = 1 - Request[0x08] = 6; // Subblock ID = 6 = Create Partition Backup to RAM - Buffer.BlockCopy(BigEndian.GetBytes(73, 2), 0, Request, 0x09, 2); // Subblock length = Length of data in subblock including fillers (subblock-ID-field and subblock-length-field are not counted for the subblock-length) - System.Text.Encoding.Unicode.GetBytes(PartitionName); - - byte[] PartitionBytes = System.Text.Encoding.Unicode.GetBytes(PartitionName); - Buffer.BlockCopy(PartitionBytes, 0, Request, 0x0C, PartitionBytes.Length); - Request[0x0C + PartitionBytes.Length + 0x00] = 0; // Trailing zero - Request[0x0C + PartitionBytes.Length + 0x01] = 0; - - byte[] Response = ExecuteRawMethod(Request); - int ResultCode = (Response[6] << 8) + Response[7]; - if (ResultCode != 0) - { - ThrowFlashError(ResultCode); - } - } - else - { - throw new WPinternalsException("Specified partition cannot be backupped to RAM", "Partition name: \"" + PartitionName + "\"."); - } - } - - public void LoadMmosBinary(UInt32 TotalLength, UInt32 Offset, bool SkipMmosSupportCheck, byte[] MmosPart) - { - byte[] Request = new byte[MmosPart.Length + 0x20]; - const string Header = "NOKXFL"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Request[0x07] = 1; // Subblock count = 1 - Buffer.BlockCopy(BigEndian.GetBytes(0x0000001E, 4), 0, Request, 0x08, 4); // Subblock ID = Load MMOS Binary = 0x1E - Buffer.BlockCopy(BigEndian.GetBytes(MmosPart.Length + 0x10, 4), 0, Request, 0x0C, 4); // Subblock length = Payload-length + 0x10 - Buffer.BlockCopy(BigEndian.GetBytes(TotalLength, 4), 0, Request, 0x10, 4); - Buffer.BlockCopy(BigEndian.GetBytes(Offset, 4), 0, Request, 0x14, 4); - if (SkipMmosSupportCheck) - { - Request[0x18] = 1; - } - - Buffer.BlockCopy(BigEndian.GetBytes(MmosPart.Length, 4), 0, Request, 0x1C, 4); - Buffer.BlockCopy(MmosPart, 0, Request, 0x20, MmosPart.Length); - - byte[] Response = ExecuteRawMethod(Request); - int ResultCode = (Response[6] << 8) + Response[7]; - if (ResultCode != 0) - { - ThrowFlashError(ResultCode); - } - } - - internal void SwitchToMmosContext() - { - byte[] Request = new byte[7]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXCBA"); - ExecuteRawVoidMethod(Request); - - ResetDevice(); - - Dispose(true); - } - - private void ThrowFlashError(int ErrorCode) - { - string SubMessage = ErrorCode switch - { - 0x0008 => "Unsupported protocol / Invalid options", - 0x000F => "Invalid sub block count", - 0x0010 => "Invalid sub block length", - 0x0012 => "Authentication required", - 0x000E => "Invalid sub block type", - 0x0013 => "Failed async message", - 0x1000 => "Invalid header type", - 0x1001 => "FFU header contain unknown extra data", - 0x0001 => "Couldn't allocate memory", - 0x1106 => "Security header validation failed", - 0x1105 => "Invalid hash table size", - 0x1104 => "Invalid catalog size", - 0x1103 => "Invalid chunk size", - 0x1102 => "Unsupported algorithm", - 0x1101 => "Invalid struct size", - 0x1100 => "Invalid signature", - 0x1202 => "Invalid struct size", - 0x1203 => "Unsupported algorithm", - 0x1204 => "Invalid chunk size", - 0x1005 => "Data not aligned correctly", - 0x0009 => "Locate protocol failed", - 0x1003 => "Hash mismatch", - 0x1006 => "Couldn't find hash from security header for index", - 0x1004 => "Security header import missing / All FFU headers have not been imported", - 0x1304 => "Invalid platform ID", - 0x1307 => "Invalid write descriptor info", - 0x1306 => "Invalid write descriptor info", - 0x1305 => "Invalid block size", - 0x1303 => "Unsupported FFU version", - 0x1302 => "Unsupported struct version", - 0x1301 => "Invalid update type", - 0x100B => "Too much payload data, all data has already been written", - 0x1008 => "Internal error", - 0x1007 => "Payload data does not contain all data", - 0x0004 => "Flash write failed", - 0x000D => "Flash verify failed", - 0x0002 => "Flash read failed", - _ => "Unknown error", - }; - WPinternalsException Ex = new("Flash failed!"); - Ex.SubMessage = "Error 0x" + ErrorCode.ToString("X4") + ": " + SubMessage; - - throw Ex; - } - - public void FlashFFU(string FFUPath, bool ResetAfterwards = true, byte Options = 0) - { - FlashFFU(new FFU(FFUPath), ResetAfterwards, Options); - } - - public void FlashFFU(FFU FFU, bool ResetAfterwards = true, byte Options = 0) - { - FlashFFU(FFU, null, ResetAfterwards, Options); - } - - public void FlashFFU(FFU FFU, ProgressUpdater UpdaterPerChunk, bool ResetAfterwards = true, byte Options = 0) - { - LogFile.BeginAction("FlashFFU"); - - ProgressUpdater Progress = UpdaterPerChunk; - - PhoneInfo Info = ReadPhoneInfo(); - if ((Info.SecureFfuSupportedProtocolMask & ((ushort)FfuProtocol.ProtocolSyncV1 | (ushort)FfuProtocol.ProtocolSyncV2)) == 0) - { - throw new WPinternalsException("Flash failed!", "Protocols not supported. The device reports that both Protocol Sync v1 and Protocol Sync v2 are not supported for FFU flashing. Is this an old device?"); - } - - UInt64 CombinedFFUHeaderSize = FFU.HeaderSize; - byte[] FfuHeader = new byte[CombinedFFUHeaderSize]; - using (FileStream FfuFile = new(FFU.Path, FileMode.Open, FileAccess.Read)) - { - FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); - SendFfuHeaderV1(FfuHeader, Options); - - UInt64 Position = CombinedFFUHeaderSize; - byte[] Payload; - int ChunkCount = 0; - - if ((Info.SecureFfuSupportedProtocolMask & (ushort)FfuProtocol.ProtocolSyncV2) == 0) - { - // Protocol v1 - Payload = new byte[FFU.ChunkSize]; - - while (Position < (UInt64)FfuFile.Length) - { - FfuFile.Read(Payload, 0, Payload.Length); - ChunkCount++; - SendFfuPayloadV1(Payload, (int)((double)ChunkCount * 100 / FFU.TotalChunkCount), 0); - Position += (ulong)Payload.Length; - - Progress?.IncreaseProgress(1); - } - } - else - { - // Protocol v2 - Payload = new byte[Info.WriteBufferSize]; - - while (Position < (UInt64)FfuFile.Length) - { - UInt32 PayloadSize = Info.WriteBufferSize; - if (((UInt64)FfuFile.Length - Position) < PayloadSize) - { - PayloadSize = (UInt32)((UInt64)FfuFile.Length - Position); - Payload = new byte[PayloadSize]; - } - - FfuFile.Read(Payload, 0, (int)PayloadSize); - ChunkCount += (int)(PayloadSize / FFU.ChunkSize); - SendFfuPayloadV2(Payload, (int)((double)ChunkCount * 100 / FFU.TotalChunkCount), 0); - Position += PayloadSize; - - Progress?.IncreaseProgress((ulong)(PayloadSize / FFU.ChunkSize)); - } - } - } - - if (ResetAfterwards) - { - ResetPhone(); - } - - LogFile.EndAction("FlashFFU"); - } - - public void FlashMMOS(string MMOSPath, ProgressUpdater UpdaterPerChunk) - { - LogFile.BeginAction("FlashMMOS"); - - ProgressUpdater Progress = UpdaterPerChunk; - - PhoneInfo Info = ReadPhoneInfo(); - if (!Info.MmosOverUsbSupported) - { - throw new WPinternalsException("Flash failed!", "Protocols not supported. The device reports that loading Microsoft Manufacturing Operating System over USB is not supported."); - } - - FileInfo info = new(MMOSPath); - uint length = uint.Parse(info.Length.ToString()); - - int offset = 0; - const int maximumbuffersize = 0x00240000; - - uint totalcounts = (uint)Math.Truncate((decimal)length / maximumbuffersize); - - using (FileStream MMOSFile = new(MMOSPath, FileMode.Open, FileAccess.Read)) - { - for (int i = 1; i <= (uint)Math.Truncate((decimal)length / maximumbuffersize); i++) - { - Progress.IncreaseProgress(1); - byte[] data = new byte[maximumbuffersize]; - MMOSFile.Read(data, 0, maximumbuffersize); - - LoadMmosBinary(length, (uint)offset, false, data); - - offset += maximumbuffersize; - } - - if (length - offset != 0) - { - Progress.IncreaseProgress(1); - - byte[] data = new byte[length - offset]; - MMOSFile.Read(data, 0, (int)(length - offset)); - LoadMmosBinary(length, (uint)offset, false, data); - } - - SwitchToMmosContext(); - ResetPhone(); - } - - LogFile.EndAction("FlashMMOS"); - } - - public void FlashSectors(UInt32 StartSector, byte[] Data, int Progress = 0) - { - // Start sector is in UInt32, so max size of eMMC is 2 TB. - - byte[] Request = new byte[Data.Length + 0x40]; - - const string Header = "NOKF"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Request[0x05] = 0; // Device type = 0 - Buffer.BlockCopy(BigEndian.GetBytes(StartSector, 4), 0, Request, 0x0B, 4); // Start sector - Buffer.BlockCopy(BigEndian.GetBytes(Data.Length / 0x200, 4), 0, Request, 0x0F, 4); // Sector count - Request[0x13] = (byte)Progress; // Progress (0 - 100) - Request[0x18] = 0; // Do Verify - Request[0x19] = 0; // Is Test - - Buffer.BlockCopy(Data, 0, Request, 0x40, Data.Length); - - ExecuteRawMethod(Request); - } - - public void DisableRebootTimeOut() - { - byte[] Request = new byte[4]; - const string Header = "NOKD"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - ExecuteRawMethod(Request); - } - - public void Shutdown() - { - byte[] Request = new byte[4]; - const string Header = "NOKZ"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - ExecuteRawMethod(Request); - } - - public FlashVersion GetFlashVersion() - { - byte[] Response = ReadParam("FAI"); - if ((Response == null) || (Response.Length < 6)) - { - return null; - } - - FlashVersion Result = new(); - - Result.ProtocolMajor = Response[1]; - Result.ProtocolMinor = Response[2]; - Result.ApplicationMajor = Response[3]; - Result.ApplicationMinor = Response[4]; - - return Result; - } - - internal GPT ReadGPT() - { - // If this function is used with a locked BootMgr v1, - // then the mode-switching should be done outside this function, - // because the context-switches that are used here are not supported on BootMgr v1. - - // Only works in BootLoader-mode or on unlocked bootloaders in Flash-mode!! - - PhoneInfo Info = ReadPhoneInfo(ExtendedInfo: false); - FlashAppType OriginalAppType = Info.App; - bool Switch = (Info.App != FlashAppType.BootManager) && Info.IsBootloaderSecure; - if (Switch) - { - SwitchToBootManagerContext(); - } - - byte[] Request = new byte[0x04]; - const string Header = "NOKT"; - - System.Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - - byte[] Buffer = ExecuteRawMethod(Request); - if ((Buffer == null) || (Buffer.Length < 0x4408)) - { - throw new InvalidOperationException("Unable to read GPT!"); - } - - UInt16 Error = (UInt16)((Buffer[6] << 8) + Buffer[7]); - if (Error > 0) - { - throw new NotSupportedException("ReadGPT: Error 0x" + Error.ToString("X4")); - } - - byte[] GPTBuffer = new byte[Buffer.Length - 0x208]; - System.Buffer.BlockCopy(Buffer, 0x208, GPTBuffer, 0, 0x4200); - - if (Switch) - { - if (OriginalAppType == FlashAppType.FlashApp) - { - SwitchToFlashAppContext(); - } - else - { - SwitchToPhoneInfoAppContext(); - } - } - - return new GPT(GPTBuffer); // NOKT message header and MBR are ignored - } - - internal void WriteGPT(GPT NewGPT) - { - bool? unlocked = IsBootLoaderUnlocked(); - if (unlocked == false) - { - throw new InvalidOperationException("Bootloader is not unlocked!"); - } - - byte[] Buffer = NewGPT.Rebuild(); - - UInt32? HeaderOffset = ByteOperations.FindAscii(Buffer, "EFI PART"); - if (HeaderOffset != 0) - { - throw new BadImageFormatException(); - } - - FlashSectors(1, Buffer); - } - - internal void FlashRawPartition(string Path, string PartitionName) - { - FlashRawPartition(Path, null, PartitionName, null, null); - } - - internal void FlashRawPartition(string Path, string PartitionName, Action ProgressUpdateCallback) - { - FlashRawPartition(Path, null, PartitionName, ProgressUpdateCallback, null); - } - - internal void FlashRawPartition(string Path, string PartitionName, ProgressUpdater UpdaterPerSector) - { - FlashRawPartition(Path, null, PartitionName, null, UpdaterPerSector); - } - - internal void FlashRawPartition(Stream Stream, string PartitionName, ProgressUpdater UpdaterPerSector) - { - FlashRawPartition(null, Stream, PartitionName, null, UpdaterPerSector); - } - - internal void FlashRawPartition(byte[] Buffer, string PartitionName, ProgressUpdater UpdaterPerSector) - { - FlashRawPartition(null, new MemoryStream(Buffer, false), PartitionName, null, UpdaterPerSector); - } - - private void FlashRawPartition(string Path, Stream Stream, string PartitionName, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) - { - bool? unlocked = IsBootLoaderUnlocked(); - if (unlocked == false) - { - throw new InvalidOperationException("Bootloader is not unlocked!"); - } - - GPT GPT = ReadGPT(); - - Partition Partition = GPT.Partitions.First((p) => p.Name == PartitionName); - ulong PartitionSize = (Partition.LastSector - Partition.FirstSector + 1) * 0x200; - - Stream InputStream = null; - - if (Path != null) - { - InputStream = new DecompressedStream(File.Open(Path, FileMode.Open)); - } - else if (Stream != null) - { - InputStream = Stream is DecompressedStream ? Stream : new DecompressedStream(Stream); - } - - if (InputStream != null) - { - using (InputStream) - { - UInt64? InputStreamLength = null; - try - { - InputStreamLength = (UInt64)InputStream.Length; - } - catch { } - - if ((InputStreamLength != null) && ((UInt64)InputStream.Length > PartitionSize)) - { - throw new InvalidOperationException("Partition can not be flashed, because its size is too big!"); - } - - ProgressUpdater Progress = UpdaterPerSector; - if ((Progress == null) && (ProgressUpdateCallback != null) && (InputStreamLength != null)) - { - Progress = new ProgressUpdater((UInt64)(InputStreamLength / 0x200), ProgressUpdateCallback); - } - - int ProgressPercentage = 0; - - const int FlashBufferSize = 0x200000; // Flash 8 GB phone -> buffersize 0x200000 = 11:45 min, buffersize 0x20000 = 12:30 min - byte[] FlashBuffer = new byte[FlashBufferSize]; - int BytesRead; - UInt64 i = 0; - do - { - BytesRead = InputStream.Read(FlashBuffer, 0, FlashBufferSize); - - byte[] FlashBufferFinalSize; - if (BytesRead > 0) - { - if (BytesRead == FlashBufferSize) - { - FlashBufferFinalSize = FlashBuffer; - } - else - { - FlashBufferFinalSize = new byte[BytesRead]; - Buffer.BlockCopy(FlashBuffer, 0, FlashBufferFinalSize, 0, BytesRead); - } - - FlashSectors((UInt32)(Partition.FirstSector + (i / 0x200)), FlashBufferFinalSize, ProgressPercentage); - } - - if (Progress != null) - { - Progress.IncreaseProgress((UInt64)FlashBuffer.Length / 0x200); - ProgressPercentage = Progress.ProgressPercentage; - } - - i += FlashBufferSize; - } - while (BytesRead == FlashBufferSize); - } - } - } - - internal void ErasePartition(string PartitionName) - { - // Partition "Data" can always be erased. - // Other partitions can only be erased when a valid RDC certificate is present or full SX authentication was performed. - if (PartitionName.Length > 0x23) - { - throw new ArgumentException("PartitionName cannot exceed 0x23 chars!"); - } - - byte[] Request = new byte[0x50]; - const string Header = "NOKXFP"; - Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); - Request[0x06] = 1; // Protocol version must be 1 - Request[0x07] = 0; // Device type = 0 - - byte[] PartitionBytes = System.Text.Encoding.Unicode.GetBytes(PartitionName); - Buffer.BlockCopy(PartitionBytes, 0, Request, 8, PartitionBytes.Length); - Request[0x08 + PartitionBytes.Length + 0x00] = 0; // Trailing zero - Request[0x08 + PartitionBytes.Length + 0x01] = 0; - - ExecuteRawMethod(Request); - } - - internal FlashAppType GetFlashAppType() - { - byte[] Request = new byte[4]; - ByteOperations.WriteAsciiString(Request, 0, "NOKV"); - byte[] Response = ExecuteRawMethod(Request); - if ((Response == null) || (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU")) - { - throw new NotSupportedException(); - } - - return (FlashAppType)Response[5]; - } - - internal PhoneInfo ReadPhoneInfo(bool ExtendedInfo = true) - { - // NOKH = Get Phone Info (IMEI and info from Product.dat) - Not available on some phones, like Lumia 640. - // NOKV = Info Query - - bool PhoneInfoLogged = Info.State != PhoneInfoState.Empty; - PhoneInfo Result = Info; - - if (Result.State == PhoneInfoState.Empty) - { - byte[] Request = new byte[4]; - ByteOperations.WriteAsciiString(Request, 0, "NOKV"); - byte[] Response = ExecuteRawMethod(Request); - if ((Response != null) && (ByteOperations.ReadAsciiString(Response, 0, 4) != "NOKU")) - { - Result.App = (FlashAppType)Response[5]; - - switch (Result.App) - { - case FlashAppType.BootManager: - Result.BootManagerProtocolVersionMajor = Response[6]; - Result.BootManagerProtocolVersionMinor = Response[7]; - Result.BootManagerVersionMajor = Response[8]; - Result.BootManagerVersionMinor = Response[9]; - break; - case FlashAppType.FlashApp: - Result.FlashAppProtocolVersionMajor = Response[6]; - Result.FlashAppProtocolVersionMinor = Response[7]; - Result.FlashAppVersionMajor = Response[8]; - Result.FlashAppVersionMinor = Response[9]; - break; - case FlashAppType.PhoneInfoApp: - Result.PhoneInfoAppProtocolVersionMajor = Response[6]; - Result.PhoneInfoAppProtocolVersionMinor = Response[7]; - Result.PhoneInfoAppVersionMajor = Response[8]; - Result.PhoneInfoAppVersionMinor = Response[9]; - break; - } - - byte SubblockCount = Response[10]; - int SubblockOffset = 11; - - for (int i = 0; i < SubblockCount; i++) - { - byte SubblockID = Response[SubblockOffset + 0x00]; - UInt16 SubblockLength = BigEndian.ToUInt16(Response, SubblockOffset + 0x01); - int SubblockPayloadOffset = SubblockOffset + 3; - byte SubblockVersion; - switch (SubblockID) - { - case 0x01: - Result.TransferSize = BigEndian.ToUInt32(Response, SubblockPayloadOffset); - break; - case 0x1F: - Result.MmosOverUsbSupported = Response[SubblockPayloadOffset] == 1; - break; - case 0x04: - if (Result.App == FlashAppType.BootManager) - { - Result.FlashAppProtocolVersionMajor = Response[SubblockPayloadOffset + 0x00]; - Result.FlashAppProtocolVersionMinor = Response[SubblockPayloadOffset + 0x01]; - Result.FlashAppVersionMajor = Response[SubblockPayloadOffset + 0x02]; - Result.FlashAppVersionMinor = Response[SubblockPayloadOffset + 0x03]; - } - else if (Result.App == FlashAppType.FlashApp) - { - Result.SdCardSizeInSectors = BigEndian.ToUInt32(Response, SubblockPayloadOffset); - } - break; - case 0x02: - Result.WriteBufferSize = BigEndian.ToUInt32(Response, SubblockPayloadOffset); - break; - case 0x03: - Result.EmmcSizeInSectors = BigEndian.ToUInt32(Response, SubblockPayloadOffset); - break; - case 0x05: - Result.PlatformID = ByteOperations.ReadAsciiString(Response, (uint)SubblockPayloadOffset, SubblockLength).Trim(new char[] { ' ', '\0' }); - break; - case 0x0D: - Result.AsyncSupport = Response[SubblockPayloadOffset + 1] == 1; - break; - case 0x0F: - SubblockVersion = Response[SubblockPayloadOffset]; // 0x03 - Result.PlatformSecureBootEnabled = Response[SubblockPayloadOffset + 0x01] == 0x01; - Result.SecureFfuEnabled = Response[SubblockPayloadOffset + 0x02] == 0x01; - Result.JtagDisabled = Response[SubblockPayloadOffset + 0x03] == 0x01; - Result.RdcPresent = Response[SubblockPayloadOffset + 0x04] == 0x01; - Result.Authenticated = (Response[SubblockPayloadOffset + 0x05] == 0x01) || (Response[SubblockPayloadOffset + 0x05] == 0x02); - Result.UefiSecureBootEnabled = Response[SubblockPayloadOffset + 0x06] == 0x01; - Result.SecondaryHardwareKeyPresent = Response[SubblockPayloadOffset + 0x07] == 0x01; - break; - case 0x10: - SubblockVersion = Response[SubblockPayloadOffset]; // 0x01 - Result.SecureFfuSupportedProtocolMask = BigEndian.ToUInt16(Response, SubblockPayloadOffset + 0x01); - break; - case 0x20: - // CRC header info - break; - } - SubblockOffset += SubblockLength + 3; - } - } - - Result.State = PhoneInfoState.Basic; - } - - if (ExtendedInfo && (Result.State == PhoneInfoState.Basic)) - { - FlashAppType OriginalType = Result.App; - - try - { - SwitchToPhoneInfoAppContext(); // May throw NotSupportedException - - Result.Type = ReadPhoneInfoVariable("TYPE"); - Result.ProductCode = ReadPhoneInfoVariable("CTR"); - Result.Imei = ReadPhoneInfoVariable("IMEI"); - - SwitchToFlashAppContext(); - DisableRebootTimeOut(); - } - catch { } - - if (Result.App == FlashAppType.FlashApp) - { - Result.Firmware = ReadStringParam("FVER"); - Result.RKH = ReadParam("RRKH"); - } - - try - { - if (OriginalType == FlashAppType.BootManager) - { - SwitchToBootManagerContext(); - } - } - catch { } - - Result.State = PhoneInfoState.Extended; - } - - Result.IsBootloaderSecure = !(Info.Authenticated || Info.RdcPresent || !Info.SecureFfuEnabled); - - if (!PhoneInfoLogged) - { - Result.Log(LogType.FileOnly); - } - - return Result; - } - - internal void ResetPhone() - { - LogFile.Log("Rebooting phone", LogType.FileAndConsole); - try - { - byte[] Request = new byte[4]; - ByteOperations.WriteAsciiString(Request, 0, "NOKR"); - ExecuteRawVoidMethod(Request); - } - catch - { - LogFile.Log("Sending reset-request failed", LogType.FileOnly); - LogFile.Log("Assuming automatic reset already in progress", LogType.FileOnly); - } - } - - internal void ContinueBoot() - { - LogFile.Log("Continue boot..."); - byte[] Request = new byte[4]; - ByteOperations.WriteAsciiString(Request, 0, "NOKA"); - ExecuteRawVoidMethod(Request); - } - - internal void ResetPhoneToFlashMode() - { - // This only works when the phone is in BootMgr mode. If it is already in FlashApp, it will not reboot. It only makes the phone unresponsive. - LogFile.Log("Rebooting phone to Flash mode..."); - byte[] Request = new byte[4]; - ByteOperations.WriteAsciiString(Request, 0, "NOKS"); - ExecuteRawVoidMethod(Request); - } - - internal void Hello() - { - byte[] Request = new byte[4]; - ByteOperations.WriteAsciiString(Request, 0, "NOKI"); - byte[] Response = ExecuteRawMethod(Request); - if (Response == null) - { - throw new BadConnectionException(); - } - - if (ByteOperations.ReadAsciiString(Response, 0, 4) != "NOKI") - { - throw new WPinternalsException("Bad response from phone!", "The phone did not answer properly to the Hello message sent."); - } - } - - internal UInt16 ReadSecureFfuSupportedProtocolMask() - { - return BigEndian.ToUInt16(ReadParam("SFPI"), 0); - } - - internal void SwitchToPhoneInfoAppContext() - { - byte[] Request = new byte[7]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXCBP"); - byte[] Response = ExecuteRawMethod(Request); - if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU") - { - throw new NotSupportedException(); - } - - UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]); - if (Error > 0) - { - throw new NotSupportedException("SwitchToPhoneInfoAppContext: Error 0x" + Error.ToString("X4")); - } - - DisableRebootTimeOut(); - Info.App = FlashAppType.PhoneInfoApp; - InterfaceChanged(PhoneInterfaces.Lumia_Flash); - } - - internal void SwitchToFlashAppContext() - { - // SwitchToFlashAppContext() should only be used with BootMgr v2 - // For switching from BootMgr to FlashApp, it will use NOKS - // That will switch to a charging state, whereas a normal context switch will not start charging - // The implementation of NOKS in BootMgr mode has changed in BootMgr v2 - // It does not disconnect / reconnect anymore and the apptype is changed immediately - // NOKS still doesnt return a status - - byte[] Request; - - if (Info.State == PhoneInfoState.Empty) - { - ReadPhoneInfo(ExtendedInfo: false); - } - - if (Info.App == FlashAppType.BootManager) - { - if (Info.FlashAppProtocolVersionMajor < 2) - { - // A phone with Bootloader Spec A cannot be switched from BootMgr to FlashApp. - // NOKS will make the phone unresponsive and let you wait for a new arrival, but that would require a PhoneNotifier and that is not available in this context. - // And NOKXCBF is not supported at all. - return; - } - - Request = new byte[4]; - ByteOperations.WriteAsciiString(Request, 0, "NOKS"); // This will let the phone charge - ExecuteRawVoidMethod(Request); // On phone with bootloader Spec A this triggers a reboot, so DisableRebootTimeOut() cannot be called immediately. - } - else if (Info.App == FlashAppType.PhoneInfoApp) - { - Request = new byte[7]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXCBF"); // This will stop charging the phone - byte[] Response = ExecuteRawMethod(Request); - if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU") - { - throw new NotSupportedException(); - } - - UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]); - if (Error > 0) - { - throw new NotSupportedException("SwitchToFlashAppContext: Error 0x" + Error.ToString("X4")); - } - } - - DisableRebootTimeOut(); - - Info.App = FlashAppType.FlashApp; - - // If current Info class was retrieved while in BootMgr mode, then we need to invalidate this data, because it is incomplete. - if (Info.PlatformID == null) - { - Info.State = PhoneInfoState.Empty; - } - - InterfaceChanged(PhoneInterfaces.Lumia_Flash); - } - - internal void SwitchToBootManagerContext(bool DisableTimeOut = true) - { - byte[] Request = new byte[7]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXCBB"); - byte[] Response = ExecuteRawMethod(Request); - if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU") - { - throw new NotSupportedException(); - } - - UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]); - if (Error > 0) - { - throw new NotSupportedException("SwitchToBootManagerContext: Error 0x" + Error.ToString("X4")); - } - - if (DisableTimeOut) - { - DisableRebootTimeOut(); - } - - Info.App = FlashAppType.BootManager; - InterfaceChanged(PhoneInterfaces.Lumia_Bootloader); - } - - internal string ReadPhoneInfoVariable(string VariableName) - { - // This function assumes the phone is in Phone Info App context - - byte[] Request = new byte[16]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXPH" + VariableName + "\0"); // BTR or CTR, CTR is public ProductCode - byte[] Response = ExecuteRawMethod(Request); - UInt16 Length = BigEndian.ToUInt16(Response, 6); - return ByteOperations.ReadAsciiString(Response, 8, Length).Trim(new char[] { ' ', '\0' }); - } - - internal string ReadProductCode() - { - SwitchToPhoneInfoAppContext(); - string Result = ReadPhoneInfoVariable("CTR"); - SwitchToFlashAppContext(); - return Result; - } - - internal void StartAsyncFlash() - { - byte[] Request = new byte[14]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXFFS"); - Request[8] = 1; // Protocol version must be 1 - Request[9] = 0; // Protocol type must be 0 - ExecuteRawMethod(Request); - } - - internal void EndAsyncFlash() - { - byte[] Request = new byte[7]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXFFE"); - ExecuteRawMethod(Request); - } - - internal enum SecureBootKeyType : byte - { - Retail = 0, - Engineering = 1 - } - - internal void ProvisionSecureBootKeys(SecureBootKeyType KeyType) // Only for Flashmode, not BootManager mode. - { - byte[] Request = new byte[8]; - ByteOperations.WriteAsciiString(Request, 0, "NOKXFK"); - Request[6] = 0; // Options - Request[7] = (byte)KeyType; - byte[] Response = ExecuteRawMethod(Request); - UInt32 Status = ByteOperations.ReadUInt32(Response, 6); - if (Status != 0) - { - ThrowFlashError((int)Status); - } - } - } - - internal enum FlashAppType - { - BootManager = 1, - FlashApp = 2, - PhoneInfoApp = 3 - }; - - internal enum PhoneInfoState - { - Empty, - Basic, - Extended - }; - - internal class PhoneInfo - { - public PhoneInfoState State = PhoneInfoState.Empty; - - public string Type; // Extended info - public string ProductCode; // Extended info - public string Imei; // Extended info - public string Firmware; // Extended info - public byte[] RKH; // Extended info - - public FlashAppType App; - - public byte FlashAppVersionMajor; - public byte FlashAppVersionMinor; - public byte FlashAppProtocolVersionMajor; - public byte FlashAppProtocolVersionMinor; - - public byte BootManagerVersionMajor; - public byte BootManagerVersionMinor; - public byte BootManagerProtocolVersionMajor; - public byte BootManagerProtocolVersionMinor; - - public byte PhoneInfoAppVersionMajor; - public byte PhoneInfoAppVersionMinor; - public byte PhoneInfoAppProtocolVersionMajor; - public byte PhoneInfoAppProtocolVersionMinor; - - public UInt32 TransferSize; - public bool MmosOverUsbSupported; - public UInt32 SdCardSizeInSectors; - public UInt32 WriteBufferSize; - public UInt32 EmmcSizeInSectors; - public string PlatformID; - public UInt16 SecureFfuSupportedProtocolMask; - public bool AsyncSupport; - - public bool PlatformSecureBootEnabled; - public bool SecureFfuEnabled; - public bool JtagDisabled; - public bool RdcPresent; - public bool Authenticated; - public bool UefiSecureBootEnabled; - public bool SecondaryHardwareKeyPresent; - - public bool IsBootloaderSecure; - - internal void Log(LogType Type) - { - if (State == PhoneInfoState.Extended) - { - if (this.Type != null) - { - LogFile.Log("Phone type: " + this.Type, Type); - } - - if (ProductCode != null) - { - LogFile.Log("Product code: " + ProductCode, Type); - } - - if (RKH != null) - { - LogFile.Log("Root key hash: " + Converter.ConvertHexToString(RKH, ""), Type); - } - - if (Firmware?.Length > 0) - { - LogFile.Log("Firmware version: " + Firmware, Type); - } - - if (Type != LogType.ConsoleOnly && (Imei != null)) - { - LogFile.Log("IMEI: " + Imei, LogType.FileOnly); - } - } - - switch (App) - { - case FlashAppType.BootManager: - LogFile.Log("Bootmanager: " + BootManagerVersionMajor + "." + BootManagerVersionMinor, Type); - LogFile.Log("Bootmanager protocol: " + BootManagerProtocolVersionMajor + "." + BootManagerProtocolVersionMinor, Type); - LogFile.Log("Flash app: " + FlashAppVersionMajor + "." + FlashAppVersionMinor, Type); - LogFile.Log("Flash protocol: " + FlashAppProtocolVersionMajor + "." + FlashAppProtocolVersionMinor, Type); - break; - case FlashAppType.FlashApp: - LogFile.Log("Flash app: " + FlashAppVersionMajor + "." + FlashAppVersionMinor, Type); - LogFile.Log("Flash protocol: " + FlashAppProtocolVersionMajor + "." + FlashAppProtocolVersionMinor, Type); - break; - case FlashAppType.PhoneInfoApp: - LogFile.Log("Phone info app: " + PhoneInfoAppVersionMajor + "." + PhoneInfoAppVersionMinor, Type); - LogFile.Log("Phone info protocol: " + PhoneInfoAppProtocolVersionMajor + "." + PhoneInfoAppProtocolVersionMinor, Type); - break; - } - - LogFile.Log("SecureBoot: " + ((!PlatformSecureBootEnabled || !UefiSecureBootEnabled) ? "Disabled" : "Enabled") + " (Platform Secure Boot: " + (PlatformSecureBootEnabled ? "Enabled" : "Disabled") + ", UEFI Secure Boot: " + (UefiSecureBootEnabled ? "Enabled" : "Disabled") + ")", Type); - - if ((Type == LogType.ConsoleOnly) || (Type == LogType.FileAndConsole)) - { - LogFile.Log("Flash app security: " + (!IsBootloaderSecure ? "Disabled" : "Enabled"), LogType.ConsoleOnly); - } - - if ((Type == LogType.FileOnly) || (Type == LogType.FileAndConsole)) - { - LogFile.Log("Flash app security: " + (!IsBootloaderSecure ? "Disabled" : "Enabled") + " (FFU security: " + (SecureFfuEnabled ? "Enabled" : "Disabled") + ", RDC: " + (RdcPresent ? "Present" : "Not found") + ", Authenticated: " + (Authenticated ? "True" : "False") + ")", LogType.FileOnly); - } - - LogFile.Log("JTAG: " + (JtagDisabled ? "Disabled" : "Enabled"), Type); - } - } - - internal class UefiSecurityStatusResponse - { - public byte IsTestDevice; - public bool PlatformSecureBootStatus; - public bool SecureFfuEfuseStatus; - public bool DebugStatus; - public bool RdcStatus; - public bool AuthenticationStatus; - public bool UefiSecureBootStatus; - public bool CryptoHardwareKey; - } - - internal class FlashVersion - { - public int ApplicationMajor; - public int ApplicationMinor; - public int ProtocolMajor; - public int ProtocolMinor; - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; + +namespace WPinternals +{ + internal enum FfuProtocol + { + ProtocolSyncV1 = 1, + ProtocolAsyncV1 = 2, + ProtocolSyncV2 = 4, + ProtocolAsyncV2 = 8, + ProtocolAsyncV3 = 16 + } + + [Flags] + internal enum FlashOptions : byte + { + SkipWrite = 1, + SkipHashCheck = 2, + SkipIdCheck = 4, + SkipSignatureCheck = 8 + } + + internal delegate void InterfaceChangedHandler(PhoneInterfaces NewInterface); + + internal class NokiaFlashModel : NokiaPhoneModel + { + private UefiSecurityStatusResponse _SecurityStatus = null; + private readonly PhoneInfo Info = new(); + + internal event InterfaceChangedHandler InterfaceChanged = delegate { }; + + public NokiaFlashModel(string DevicePath) : base(DevicePath) { } + + public byte[] ReadParam(string Param) + { + byte[] Request = new byte[0x0B]; + const string Header = "NOKXFR"; + + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Param), 0, Request, 7, Param.Length); + + byte[] Response = ExecuteRawMethod(Request); + if ((Response == null) || (Response.Length < 0x10)) + { + return null; + } + + byte[] Result = new byte[Response[0x10]]; + Buffer.BlockCopy(Response, 0x11, Result, 0, Response[0x10]); + return Result; + } + + public string ReadStringParam(string Param) + { + byte[] Bytes = ReadParam(Param); + if (Bytes == null) + { + return null; + } + + return System.Text.Encoding.ASCII.GetString(Bytes).Trim(new char[] { '\0' }); + } + + [Flags] + internal enum Fuse + { + SecureBoot = 1, + FfuVerify = 2, + Jtag = 4, + Shk = 8, + Simlock = 16, + ProductionDone = 32, + Rkh = 64, + PublicId = 128, + Dak = 256, + SecGen = 512, + OemId = 1024, + FastBoot = 2048, + SpdmSecMode = 4096, + RpmWdog = 8192, + Ssm = 16384 + } + + public bool? ReadFuseStatus(Fuse fuse) + { + uint? flags = ReadSecurityFlags(); + if (!flags.HasValue) + { + return null; + } + + var finalconfig = (Fuse)flags.Value; + return finalconfig.HasFlag(fuse); + } + + public uint? ReadSecurityFlags() + { + byte[] Response = ReadParam("FCS"); + if ((Response == null) || (Response.Length != 4)) + { + return null; + } + + // This value is in big endian + return (UInt32)((Response[0] << 24) | (Response[1] << 16) | (Response[2] << 8) | Response[3]); + } + + public uint? ReadCurrentChargeLevel() + { + byte[] Response = ReadParam("CS"); + if ((Response == null) || (Response.Length != 8)) + { + return null; + } + + // This value is in big endian + return (UInt32)((Response[0] << 24) | (Response[1] << 16) | (Response[2] << 8) | Response[3]) + 1; + } + + public int? ReadCurrentChargeCurrent() + { + byte[] Response = ReadParam("CS"); + if ((Response == null) || (Response.Length != 8)) + { + return null; + } + + // This value is in big endian and needs to be XOR'd with 0xFFFFFFFF + return (Int32)(((Response[4] << 24) | (Response[5] << 16) | (Response[6] << 8) | Response[7]) ^ 0xFFFFFFFF) + 1; + } + + public UefiSecurityStatusResponse ReadSecurityStatus() + { + if (_SecurityStatus != null) + { + return _SecurityStatus; + } + + byte[] Response = ReadParam("SS"); + if (Response == null) + { + return null; + } + + UefiSecurityStatusResponse Result = new(); + + Result.IsTestDevice = Response[0]; + Result.PlatformSecureBootStatus = Convert.ToBoolean(Response[1]); + Result.SecureFfuEfuseStatus = Convert.ToBoolean(Response[2]); + Result.DebugStatus = Convert.ToBoolean(Response[3]); + Result.RdcStatus = Convert.ToBoolean(Response[4]); + Result.AuthenticationStatus = Convert.ToBoolean(Response[5]); + Result.UefiSecureBootStatus = Convert.ToBoolean(Response[6]); + Result.CryptoHardwareKey = Convert.ToBoolean(Response[7]); + + _SecurityStatus = Result; + + return Result; + } + + internal bool? IsBootLoaderUnlocked() + { + UefiSecurityStatusResponse SecurityStatus = ReadSecurityStatus(); + if (SecurityStatus != null) + { + return SecurityStatus.AuthenticationStatus || SecurityStatus.RdcStatus || !SecurityStatus.SecureFfuEfuseStatus; + } + + return null; + } + + public TerminalResponse GetTerminalResponse() + { + byte[] AsskMask = new byte[0x10] { 1, 0, 16, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 }; + byte[] Request = new byte[0xAC]; + const string Header = "NOKXFT"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Request[7] = 1; + Buffer.BlockCopy(BigEndian.GetBytes(0x18, 4), 0, Request, 0x08, 4); // Subblocktype = 0x18 + Buffer.BlockCopy(BigEndian.GetBytes(0x9C, 4), 0, Request, 0x0C, 4); // Subblocklength = 0x9C + Buffer.BlockCopy(BigEndian.GetBytes(0x00, 4), 0, Request, 0x10, 4); // AsicIndex = 0x00 + Buffer.BlockCopy(AsskMask, 0, Request, 0x14, 0x10); + byte[] TerminalResponse = ExecuteRawMethod(Request); + if ((TerminalResponse?.Length > 0x20) && (BigEndian.ToUInt32(TerminalResponse, 0x14) == (TerminalResponse.Length - 0x18)) && (BitConverter.ToUInt32(TerminalResponse, 0x1C) == (TerminalResponse.Length - 0x20))) + { + // Parse Terminal Response from offset 0x18 + return Terminal.Parse(TerminalResponse, 0x18); + } + else + { + return null; + } + } + + public void SendFfuHeaderV1(byte[] FfuHeader, byte Options = 0) + { + byte[] Request = new byte[FfuHeader.Length + 0x20]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes(0x0001, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 + Request[0x08] = 0; // Progress = 0% + Request[0x0B] = 1; // Subblock count = 1 + Buffer.BlockCopy(BigEndian.GetBytes(0x0000000B, 4), 0, Request, 0x0C, 4); // Subblock type for header = 0x0B + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of header + 0x0C + Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0 + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x18, 4); // Payload length = length of header + Request[0x1C] = Options; // Header options = 0 + + Buffer.BlockCopy(FfuHeader, 0, Request, 0x20, FfuHeader.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuHeaderV2(UInt32 TotalHeaderLength, UInt32 OffsetForThisPart, byte[] FfuHeader, byte Options = 0) + { + byte[] Request = new byte[FfuHeader.Length + 0x3C]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 + Request[0x08] = 0; // Progress = 0% + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x00000021, 4), 0, Request, 0x0C, 4); // Subblock type for header v2 = 0x21 + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x28, 4), 0, Request, 0x10, 4); // Subblock starts at 0x14, payload starts at 0x3C. + + Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0 + Buffer.BlockCopy(BigEndian.GetBytes(TotalHeaderLength, 4), 0, Request, 0x18, 4); // Payload length = length of header + Request[0x1C] = Options; // Header options = 0 + + Buffer.BlockCopy(BigEndian.GetBytes(OffsetForThisPart, 4), 0, Request, 0x1D, 4); + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x21, 4); + Request[0x25] = 0; // No Erase + + Buffer.BlockCopy(FfuHeader, 0, Request, 0x3C, FfuHeader.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + if (Response.Length == 4) + { + throw new WPinternalsException("Flash protocol v2 not supported", "The device reported that the Flash protocol v2 was not supported while sending the FFU header."); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuPayloadV1(byte[] FfuChunk, int Progress = 0, byte Options = 0) + { + byte[] Request = new byte[FfuChunk.Length + 0x1C]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV1, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 + Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x0000000C, 4), 0, Request, 0x0C, 4); // Subblock type for ChunkData = 0x0C + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x08, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 + + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk + Request[0x18] = Options; // Data options = 0 (1 = verify) + + Buffer.BlockCopy(FfuChunk, 0, Request, 0x1C, FfuChunk.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuPayloadV2(byte[] FfuChunk, int Progress = 0, byte Options = 0) + { + byte[] Request = new byte[FfuChunk.Length + 0x20]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol + Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x0000001B, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 + + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk + Request[0x18] = Options; // Data options = 0 (1 = verify) + + Buffer.BlockCopy(FfuChunk, 0, Request, 0x20, FfuChunk.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuPayloadV3(byte[] FfuChunk, UInt32 WriteDescriptorIndex, UInt32 CRC, int Progress = 0, byte Options = 0) + { + byte[] Request = new byte[FfuChunk.Length + 0x20]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolAsyncV3, 2), 0, Request, 0x06, 2); // Protocol + Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x0000001D, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x2C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 + + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk + Request[0x18] = Options; // Data options = 0 (1 = verify) + Buffer.BlockCopy(BigEndian.GetBytes(WriteDescriptorIndex, 4), 0, Request, 0x19, 4); // Payload length = length of chunk + Buffer.BlockCopy(BigEndian.GetBytes(CRC, 4), 0, Request, 0x1D, 4); // Payload length = length of chunk + + Buffer.BlockCopy(FfuChunk, 0, Request, 0x40, FfuChunk.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void BackupPartitionToRam(string PartitionName) + { + PartitionName = PartitionName.ToUpper(); + if (new string[] { "MODEM_FSG", "MODEM_FS1", "MODEM_FS2", "SSD", "DPP" }.Any(s => s == PartitionName)) + { + byte[] Request = new byte[84]; + const string Header = "NOKXFB"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Request[0x07] = 1; // Subblock count = 1 + Request[0x08] = 6; // Subblock ID = 6 = Create Partition Backup to RAM + Buffer.BlockCopy(BigEndian.GetBytes(73, 2), 0, Request, 0x09, 2); // Subblock length = Length of data in subblock including fillers (subblock-ID-field and subblock-length-field are not counted for the subblock-length) + System.Text.Encoding.Unicode.GetBytes(PartitionName); + + byte[] PartitionBytes = System.Text.Encoding.Unicode.GetBytes(PartitionName); + Buffer.BlockCopy(PartitionBytes, 0, Request, 0x0C, PartitionBytes.Length); + Request[0x0C + PartitionBytes.Length + 0x00] = 0; // Trailing zero + Request[0x0C + PartitionBytes.Length + 0x01] = 0; + + byte[] Response = ExecuteRawMethod(Request); + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + else + { + throw new WPinternalsException("Specified partition cannot be backupped to RAM", "Partition name: \"" + PartitionName + "\"."); + } + } + + public void LoadMmosBinary(UInt32 TotalLength, UInt32 Offset, bool SkipMmosSupportCheck, byte[] MmosPart) + { + byte[] Request = new byte[MmosPart.Length + 0x20]; + const string Header = "NOKXFL"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Request[0x07] = 1; // Subblock count = 1 + Buffer.BlockCopy(BigEndian.GetBytes(0x0000001E, 4), 0, Request, 0x08, 4); // Subblock ID = Load MMOS Binary = 0x1E + Buffer.BlockCopy(BigEndian.GetBytes(MmosPart.Length + 0x10, 4), 0, Request, 0x0C, 4); // Subblock length = Payload-length + 0x10 + Buffer.BlockCopy(BigEndian.GetBytes(TotalLength, 4), 0, Request, 0x10, 4); + Buffer.BlockCopy(BigEndian.GetBytes(Offset, 4), 0, Request, 0x14, 4); + if (SkipMmosSupportCheck) + { + Request[0x18] = 1; + } + + Buffer.BlockCopy(BigEndian.GetBytes(MmosPart.Length, 4), 0, Request, 0x1C, 4); + Buffer.BlockCopy(MmosPart, 0, Request, 0x20, MmosPart.Length); + + byte[] Response = ExecuteRawMethod(Request); + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + internal void SwitchToMmosContext() + { + byte[] Request = new byte[7]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXCBA"); + ExecuteRawVoidMethod(Request); + + ResetDevice(); + + Dispose(true); + } + + private void ThrowFlashError(int ErrorCode) + { + string SubMessage = ErrorCode switch + { + 0x0008 => "Unsupported protocol / Invalid options", + 0x000F => "Invalid sub block count", + 0x0010 => "Invalid sub block length", + 0x0012 => "Authentication required", + 0x000E => "Invalid sub block type", + 0x0013 => "Failed async message", + 0x1000 => "Invalid header type", + 0x1001 => "FFU header contain unknown extra data", + 0x0001 => "Couldn't allocate memory", + 0x1106 => "Security header validation failed", + 0x1105 => "Invalid hash table size", + 0x1104 => "Invalid catalog size", + 0x1103 => "Invalid chunk size", + 0x1102 => "Unsupported algorithm", + 0x1101 => "Invalid struct size", + 0x1100 => "Invalid signature", + 0x1202 => "Invalid struct size", + 0x1203 => "Unsupported algorithm", + 0x1204 => "Invalid chunk size", + 0x1005 => "Data not aligned correctly", + 0x0009 => "Locate protocol failed", + 0x1003 => "Hash mismatch", + 0x1006 => "Couldn't find hash from security header for index", + 0x1004 => "Security header import missing / All FFU headers have not been imported", + 0x1304 => "Invalid platform ID", + 0x1307 => "Invalid write descriptor info", + 0x1306 => "Invalid write descriptor info", + 0x1305 => "Invalid block size", + 0x1303 => "Unsupported FFU version", + 0x1302 => "Unsupported struct version", + 0x1301 => "Invalid update type", + 0x100B => "Too much payload data, all data has already been written", + 0x1008 => "Internal error", + 0x1007 => "Payload data does not contain all data", + 0x0004 => "Flash write failed", + 0x000D => "Flash verify failed", + 0x0002 => "Flash read failed", + _ => "Unknown error", + }; + WPinternalsException Ex = new("Flash failed!"); + Ex.SubMessage = "Error 0x" + ErrorCode.ToString("X4") + ": " + SubMessage; + + throw Ex; + } + + public void FlashFFU(string FFUPath, bool ResetAfterwards = true, byte Options = 0) + { + FlashFFU(new FFU(FFUPath), ResetAfterwards, Options); + } + + public void FlashFFU(FFU FFU, bool ResetAfterwards = true, byte Options = 0) + { + FlashFFU(FFU, null, ResetAfterwards, Options); + } + + public void FlashFFU(FFU FFU, ProgressUpdater UpdaterPerChunk, bool ResetAfterwards = true, byte Options = 0) + { + LogFile.BeginAction("FlashFFU"); + + ProgressUpdater Progress = UpdaterPerChunk; + + PhoneInfo Info = ReadPhoneInfo(); + if ((Info.SecureFfuSupportedProtocolMask & ((ushort)FfuProtocol.ProtocolSyncV1 | (ushort)FfuProtocol.ProtocolSyncV2)) == 0) + { + throw new WPinternalsException("Flash failed!", "Protocols not supported. The device reports that both Protocol Sync v1 and Protocol Sync v2 are not supported for FFU flashing. Is this an old device?"); + } + + UInt64 CombinedFFUHeaderSize = FFU.HeaderSize; + byte[] FfuHeader = new byte[CombinedFFUHeaderSize]; + using (FileStream FfuFile = new(FFU.Path, FileMode.Open, FileAccess.Read)) + { + FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); + SendFfuHeaderV1(FfuHeader, Options); + + UInt64 Position = CombinedFFUHeaderSize; + byte[] Payload; + int ChunkCount = 0; + + if ((Info.SecureFfuSupportedProtocolMask & (ushort)FfuProtocol.ProtocolSyncV2) == 0) + { + // Protocol v1 + Payload = new byte[FFU.ChunkSize]; + + while (Position < (UInt64)FfuFile.Length) + { + FfuFile.Read(Payload, 0, Payload.Length); + ChunkCount++; + SendFfuPayloadV1(Payload, (int)((double)ChunkCount * 100 / FFU.TotalChunkCount), 0); + Position += (ulong)Payload.Length; + + Progress?.IncreaseProgress(1); + } + } + else + { + // Protocol v2 + Payload = new byte[Info.WriteBufferSize]; + + while (Position < (UInt64)FfuFile.Length) + { + UInt32 PayloadSize = Info.WriteBufferSize; + if (((UInt64)FfuFile.Length - Position) < PayloadSize) + { + PayloadSize = (UInt32)((UInt64)FfuFile.Length - Position); + Payload = new byte[PayloadSize]; + } + + FfuFile.Read(Payload, 0, (int)PayloadSize); + ChunkCount += (int)(PayloadSize / FFU.ChunkSize); + SendFfuPayloadV2(Payload, (int)((double)ChunkCount * 100 / FFU.TotalChunkCount), 0); + Position += PayloadSize; + + Progress?.IncreaseProgress((ulong)(PayloadSize / FFU.ChunkSize)); + } + } + } + + if (ResetAfterwards) + { + ResetPhone(); + } + + LogFile.EndAction("FlashFFU"); + } + + public void FlashMMOS(string MMOSPath, ProgressUpdater UpdaterPerChunk) + { + LogFile.BeginAction("FlashMMOS"); + + ProgressUpdater Progress = UpdaterPerChunk; + + PhoneInfo Info = ReadPhoneInfo(); + if (!Info.MmosOverUsbSupported) + { + throw new WPinternalsException("Flash failed!", "Protocols not supported. The device reports that loading Microsoft Manufacturing Operating System over USB is not supported."); + } + + FileInfo info = new(MMOSPath); + uint length = uint.Parse(info.Length.ToString()); + + int offset = 0; + const int maximumbuffersize = 0x00240000; + + uint totalcounts = (uint)Math.Truncate((decimal)length / maximumbuffersize); + + using (FileStream MMOSFile = new(MMOSPath, FileMode.Open, FileAccess.Read)) + { + for (int i = 1; i <= (uint)Math.Truncate((decimal)length / maximumbuffersize); i++) + { + Progress.IncreaseProgress(1); + byte[] data = new byte[maximumbuffersize]; + MMOSFile.Read(data, 0, maximumbuffersize); + + LoadMmosBinary(length, (uint)offset, false, data); + + offset += maximumbuffersize; + } + + if (length - offset != 0) + { + Progress.IncreaseProgress(1); + + byte[] data = new byte[length - offset]; + MMOSFile.Read(data, 0, (int)(length - offset)); + LoadMmosBinary(length, (uint)offset, false, data); + } + + SwitchToMmosContext(); + ResetPhone(); + } + + LogFile.EndAction("FlashMMOS"); + } + + public void FlashSectors(UInt32 StartSector, byte[] Data, int Progress = 0) + { + // Start sector is in UInt32, so max size of eMMC is 2 TB. + + byte[] Request = new byte[Data.Length + 0x40]; + + const string Header = "NOKF"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Request[0x05] = 0; // Device type = 0 + Buffer.BlockCopy(BigEndian.GetBytes(StartSector, 4), 0, Request, 0x0B, 4); // Start sector + Buffer.BlockCopy(BigEndian.GetBytes(Data.Length / 0x200, 4), 0, Request, 0x0F, 4); // Sector count + Request[0x13] = (byte)Progress; // Progress (0 - 100) + Request[0x18] = 0; // Do Verify + Request[0x19] = 0; // Is Test + + Buffer.BlockCopy(Data, 0, Request, 0x40, Data.Length); + + ExecuteRawMethod(Request); + } + + public void DisableRebootTimeOut() + { + byte[] Request = new byte[4]; + const string Header = "NOKD"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + ExecuteRawMethod(Request); + } + + public void Shutdown() + { + byte[] Request = new byte[4]; + const string Header = "NOKZ"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + ExecuteRawMethod(Request); + } + + public FlashVersion GetFlashVersion() + { + byte[] Response = ReadParam("FAI"); + if ((Response == null) || (Response.Length < 6)) + { + return null; + } + + FlashVersion Result = new(); + + Result.ProtocolMajor = Response[1]; + Result.ProtocolMinor = Response[2]; + Result.ApplicationMajor = Response[3]; + Result.ApplicationMinor = Response[4]; + + return Result; + } + + internal GPT ReadGPT() + { + // If this function is used with a locked BootMgr v1, + // then the mode-switching should be done outside this function, + // because the context-switches that are used here are not supported on BootMgr v1. + + // Only works in BootLoader-mode or on unlocked bootloaders in Flash-mode!! + + PhoneInfo Info = ReadPhoneInfo(ExtendedInfo: false); + FlashAppType OriginalAppType = Info.App; + bool Switch = (Info.App != FlashAppType.BootManager) && Info.IsBootloaderSecure; + if (Switch) + { + SwitchToBootManagerContext(); + } + + byte[] Request = new byte[0x04]; + const string Header = "NOKT"; + + System.Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + + byte[] Buffer = ExecuteRawMethod(Request); + if ((Buffer == null) || (Buffer.Length < 0x4408)) + { + throw new InvalidOperationException("Unable to read GPT!"); + } + + UInt16 Error = (UInt16)((Buffer[6] << 8) + Buffer[7]); + if (Error > 0) + { + throw new NotSupportedException("ReadGPT: Error 0x" + Error.ToString("X4")); + } + + byte[] GPTBuffer = new byte[Buffer.Length - 0x208]; + System.Buffer.BlockCopy(Buffer, 0x208, GPTBuffer, 0, 0x4200); + + if (Switch) + { + if (OriginalAppType == FlashAppType.FlashApp) + { + SwitchToFlashAppContext(); + } + else + { + SwitchToPhoneInfoAppContext(); + } + } + + return new GPT(GPTBuffer); // NOKT message header and MBR are ignored + } + + internal void WriteGPT(GPT NewGPT) + { + bool? unlocked = IsBootLoaderUnlocked(); + if (unlocked == false) + { + throw new InvalidOperationException("Bootloader is not unlocked!"); + } + + byte[] Buffer = NewGPT.Rebuild(); + + UInt32? HeaderOffset = ByteOperations.FindAscii(Buffer, "EFI PART"); + if (HeaderOffset != 0) + { + throw new BadImageFormatException(); + } + + FlashSectors(1, Buffer); + } + + internal void FlashRawPartition(string Path, string PartitionName) + { + FlashRawPartition(Path, null, PartitionName, null, null); + } + + internal void FlashRawPartition(string Path, string PartitionName, Action ProgressUpdateCallback) + { + FlashRawPartition(Path, null, PartitionName, ProgressUpdateCallback, null); + } + + internal void FlashRawPartition(string Path, string PartitionName, ProgressUpdater UpdaterPerSector) + { + FlashRawPartition(Path, null, PartitionName, null, UpdaterPerSector); + } + + internal void FlashRawPartition(Stream Stream, string PartitionName, ProgressUpdater UpdaterPerSector) + { + FlashRawPartition(null, Stream, PartitionName, null, UpdaterPerSector); + } + + internal void FlashRawPartition(byte[] Buffer, string PartitionName, ProgressUpdater UpdaterPerSector) + { + FlashRawPartition(null, new MemoryStream(Buffer, false), PartitionName, null, UpdaterPerSector); + } + + private void FlashRawPartition(string Path, Stream Stream, string PartitionName, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector) + { + bool? unlocked = IsBootLoaderUnlocked(); + if (unlocked == false) + { + throw new InvalidOperationException("Bootloader is not unlocked!"); + } + + GPT GPT = ReadGPT(); + + Partition Partition = GPT.Partitions.First((p) => p.Name == PartitionName); + ulong PartitionSize = (Partition.LastSector - Partition.FirstSector + 1) * 0x200; + + Stream InputStream = null; + + if (Path != null) + { + InputStream = new DecompressedStream(File.Open(Path, FileMode.Open)); + } + else if (Stream != null) + { + InputStream = Stream is DecompressedStream ? Stream : new DecompressedStream(Stream); + } + + if (InputStream != null) + { + using (InputStream) + { + UInt64? InputStreamLength = null; + try + { + InputStreamLength = (UInt64)InputStream.Length; + } + catch { } + + if ((InputStreamLength != null) && ((UInt64)InputStream.Length > PartitionSize)) + { + throw new InvalidOperationException("Partition can not be flashed, because its size is too big!"); + } + + ProgressUpdater Progress = UpdaterPerSector; + if ((Progress == null) && (ProgressUpdateCallback != null) && (InputStreamLength != null)) + { + Progress = new ProgressUpdater((UInt64)(InputStreamLength / 0x200), ProgressUpdateCallback); + } + + int ProgressPercentage = 0; + + const int FlashBufferSize = 0x200000; // Flash 8 GB phone -> buffersize 0x200000 = 11:45 min, buffersize 0x20000 = 12:30 min + byte[] FlashBuffer = new byte[FlashBufferSize]; + int BytesRead; + UInt64 i = 0; + do + { + BytesRead = InputStream.Read(FlashBuffer, 0, FlashBufferSize); + + byte[] FlashBufferFinalSize; + if (BytesRead > 0) + { + if (BytesRead == FlashBufferSize) + { + FlashBufferFinalSize = FlashBuffer; + } + else + { + FlashBufferFinalSize = new byte[BytesRead]; + Buffer.BlockCopy(FlashBuffer, 0, FlashBufferFinalSize, 0, BytesRead); + } + + FlashSectors((UInt32)(Partition.FirstSector + (i / 0x200)), FlashBufferFinalSize, ProgressPercentage); + } + + if (Progress != null) + { + Progress.IncreaseProgress((UInt64)FlashBuffer.Length / 0x200); + ProgressPercentage = Progress.ProgressPercentage; + } + + i += FlashBufferSize; + } + while (BytesRead == FlashBufferSize); + } + } + } + + internal void ErasePartition(string PartitionName) + { + // Partition "Data" can always be erased. + // Other partitions can only be erased when a valid RDC certificate is present or full SX authentication was performed. + if (PartitionName.Length > 0x23) + { + throw new ArgumentException("PartitionName cannot exceed 0x23 chars!"); + } + + byte[] Request = new byte[0x50]; + const string Header = "NOKXFP"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Request[0x06] = 1; // Protocol version must be 1 + Request[0x07] = 0; // Device type = 0 + + byte[] PartitionBytes = System.Text.Encoding.Unicode.GetBytes(PartitionName); + Buffer.BlockCopy(PartitionBytes, 0, Request, 8, PartitionBytes.Length); + Request[0x08 + PartitionBytes.Length + 0x00] = 0; // Trailing zero + Request[0x08 + PartitionBytes.Length + 0x01] = 0; + + ExecuteRawMethod(Request); + } + + internal FlashAppType GetFlashAppType() + { + byte[] Request = new byte[4]; + ByteOperations.WriteAsciiString(Request, 0, "NOKV"); + byte[] Response = ExecuteRawMethod(Request); + if ((Response == null) || (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU")) + { + throw new NotSupportedException(); + } + + return (FlashAppType)Response[5]; + } + + internal PhoneInfo ReadPhoneInfo(bool ExtendedInfo = true) + { + // NOKH = Get Phone Info (IMEI and info from Product.dat) - Not available on some phones, like Lumia 640. + // NOKV = Info Query + + bool PhoneInfoLogged = Info.State != PhoneInfoState.Empty; + PhoneInfo Result = Info; + + if (Result.State == PhoneInfoState.Empty) + { + byte[] Request = new byte[4]; + ByteOperations.WriteAsciiString(Request, 0, "NOKV"); + byte[] Response = ExecuteRawMethod(Request); + if ((Response != null) && (ByteOperations.ReadAsciiString(Response, 0, 4) != "NOKU")) + { + Result.App = (FlashAppType)Response[5]; + + switch (Result.App) + { + case FlashAppType.BootManager: + Result.BootManagerProtocolVersionMajor = Response[6]; + Result.BootManagerProtocolVersionMinor = Response[7]; + Result.BootManagerVersionMajor = Response[8]; + Result.BootManagerVersionMinor = Response[9]; + break; + case FlashAppType.FlashApp: + Result.FlashAppProtocolVersionMajor = Response[6]; + Result.FlashAppProtocolVersionMinor = Response[7]; + Result.FlashAppVersionMajor = Response[8]; + Result.FlashAppVersionMinor = Response[9]; + break; + case FlashAppType.PhoneInfoApp: + Result.PhoneInfoAppProtocolVersionMajor = Response[6]; + Result.PhoneInfoAppProtocolVersionMinor = Response[7]; + Result.PhoneInfoAppVersionMajor = Response[8]; + Result.PhoneInfoAppVersionMinor = Response[9]; + break; + } + + byte SubblockCount = Response[10]; + int SubblockOffset = 11; + + for (int i = 0; i < SubblockCount; i++) + { + byte SubblockID = Response[SubblockOffset + 0x00]; + UInt16 SubblockLength = BigEndian.ToUInt16(Response, SubblockOffset + 0x01); + int SubblockPayloadOffset = SubblockOffset + 3; + byte SubblockVersion; + switch (SubblockID) + { + case 0x01: + Result.TransferSize = BigEndian.ToUInt32(Response, SubblockPayloadOffset); + break; + case 0x1F: + Result.MmosOverUsbSupported = Response[SubblockPayloadOffset] == 1; + break; + case 0x04: + if (Result.App == FlashAppType.BootManager) + { + Result.FlashAppProtocolVersionMajor = Response[SubblockPayloadOffset + 0x00]; + Result.FlashAppProtocolVersionMinor = Response[SubblockPayloadOffset + 0x01]; + Result.FlashAppVersionMajor = Response[SubblockPayloadOffset + 0x02]; + Result.FlashAppVersionMinor = Response[SubblockPayloadOffset + 0x03]; + } + else if (Result.App == FlashAppType.FlashApp) + { + Result.SdCardSizeInSectors = BigEndian.ToUInt32(Response, SubblockPayloadOffset); + } + break; + case 0x02: + Result.WriteBufferSize = BigEndian.ToUInt32(Response, SubblockPayloadOffset); + break; + case 0x03: + Result.EmmcSizeInSectors = BigEndian.ToUInt32(Response, SubblockPayloadOffset); + break; + case 0x05: + Result.PlatformID = ByteOperations.ReadAsciiString(Response, (uint)SubblockPayloadOffset, SubblockLength).Trim(new char[] { ' ', '\0' }); + break; + case 0x0D: + Result.AsyncSupport = Response[SubblockPayloadOffset + 1] == 1; + break; + case 0x0F: + SubblockVersion = Response[SubblockPayloadOffset]; // 0x03 + Result.PlatformSecureBootEnabled = Response[SubblockPayloadOffset + 0x01] == 0x01; + Result.SecureFfuEnabled = Response[SubblockPayloadOffset + 0x02] == 0x01; + Result.JtagDisabled = Response[SubblockPayloadOffset + 0x03] == 0x01; + Result.RdcPresent = Response[SubblockPayloadOffset + 0x04] == 0x01; + Result.Authenticated = (Response[SubblockPayloadOffset + 0x05] == 0x01) || (Response[SubblockPayloadOffset + 0x05] == 0x02); + Result.UefiSecureBootEnabled = Response[SubblockPayloadOffset + 0x06] == 0x01; + Result.SecondaryHardwareKeyPresent = Response[SubblockPayloadOffset + 0x07] == 0x01; + break; + case 0x10: + SubblockVersion = Response[SubblockPayloadOffset]; // 0x01 + Result.SecureFfuSupportedProtocolMask = BigEndian.ToUInt16(Response, SubblockPayloadOffset + 0x01); + break; + case 0x20: + // CRC header info + break; + } + SubblockOffset += SubblockLength + 3; + } + } + + Result.State = PhoneInfoState.Basic; + } + + if (ExtendedInfo && (Result.State == PhoneInfoState.Basic)) + { + FlashAppType OriginalType = Result.App; + + try + { + SwitchToPhoneInfoAppContext(); // May throw NotSupportedException + + Result.Type = ReadPhoneInfoVariable("TYPE"); + Result.ProductCode = ReadPhoneInfoVariable("CTR"); + Result.Imei = ReadPhoneInfoVariable("IMEI"); + + SwitchToFlashAppContext(); + DisableRebootTimeOut(); + } + catch { } + + if (Result.App == FlashAppType.FlashApp) + { + Result.Firmware = ReadStringParam("FVER"); + Result.RKH = ReadParam("RRKH"); + } + + try + { + if (OriginalType == FlashAppType.BootManager) + { + SwitchToBootManagerContext(); + } + } + catch { } + + Result.State = PhoneInfoState.Extended; + } + + Result.IsBootloaderSecure = !(Info.Authenticated || Info.RdcPresent || !Info.SecureFfuEnabled); + + if (!PhoneInfoLogged) + { + Result.Log(LogType.FileOnly); + } + + return Result; + } + + internal void ResetPhone() + { + LogFile.Log("Rebooting phone", LogType.FileAndConsole); + try + { + byte[] Request = new byte[4]; + ByteOperations.WriteAsciiString(Request, 0, "NOKR"); + ExecuteRawVoidMethod(Request); + } + catch + { + LogFile.Log("Sending reset-request failed", LogType.FileOnly); + LogFile.Log("Assuming automatic reset already in progress", LogType.FileOnly); + } + } + + internal void ContinueBoot() + { + LogFile.Log("Continue boot..."); + byte[] Request = new byte[4]; + ByteOperations.WriteAsciiString(Request, 0, "NOKA"); + ExecuteRawVoidMethod(Request); + } + + internal void ResetPhoneToFlashMode() + { + // This only works when the phone is in BootMgr mode. If it is already in FlashApp, it will not reboot. It only makes the phone unresponsive. + LogFile.Log("Rebooting phone to Flash mode..."); + byte[] Request = new byte[4]; + ByteOperations.WriteAsciiString(Request, 0, "NOKS"); + ExecuteRawVoidMethod(Request); + } + + internal void Hello() + { + byte[] Request = new byte[4]; + ByteOperations.WriteAsciiString(Request, 0, "NOKI"); + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + if (ByteOperations.ReadAsciiString(Response, 0, 4) != "NOKI") + { + throw new WPinternalsException("Bad response from phone!", "The phone did not answer properly to the Hello message sent."); + } + } + + internal UInt16 ReadSecureFfuSupportedProtocolMask() + { + return BigEndian.ToUInt16(ReadParam("SFPI"), 0); + } + + internal void SwitchToPhoneInfoAppContext() + { + byte[] Request = new byte[7]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXCBP"); + byte[] Response = ExecuteRawMethod(Request); + if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU") + { + throw new NotSupportedException(); + } + + UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]); + if (Error > 0) + { + throw new NotSupportedException("SwitchToPhoneInfoAppContext: Error 0x" + Error.ToString("X4")); + } + + DisableRebootTimeOut(); + Info.App = FlashAppType.PhoneInfoApp; + InterfaceChanged(PhoneInterfaces.Lumia_Flash); + } + + internal void SwitchToFlashAppContext() + { + // SwitchToFlashAppContext() should only be used with BootMgr v2 + // For switching from BootMgr to FlashApp, it will use NOKS + // That will switch to a charging state, whereas a normal context switch will not start charging + // The implementation of NOKS in BootMgr mode has changed in BootMgr v2 + // It does not disconnect / reconnect anymore and the apptype is changed immediately + // NOKS still doesnt return a status + + byte[] Request; + + if (Info.State == PhoneInfoState.Empty) + { + ReadPhoneInfo(ExtendedInfo: false); + } + + if (Info.App == FlashAppType.BootManager) + { + if (Info.FlashAppProtocolVersionMajor < 2) + { + // A phone with Bootloader Spec A cannot be switched from BootMgr to FlashApp. + // NOKS will make the phone unresponsive and let you wait for a new arrival, but that would require a PhoneNotifier and that is not available in this context. + // And NOKXCBF is not supported at all. + return; + } + + Request = new byte[4]; + ByteOperations.WriteAsciiString(Request, 0, "NOKS"); // This will let the phone charge + ExecuteRawVoidMethod(Request); // On phone with bootloader Spec A this triggers a reboot, so DisableRebootTimeOut() cannot be called immediately. + } + else if (Info.App == FlashAppType.PhoneInfoApp) + { + Request = new byte[7]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXCBF"); // This will stop charging the phone + byte[] Response = ExecuteRawMethod(Request); + if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU") + { + throw new NotSupportedException(); + } + + UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]); + if (Error > 0) + { + throw new NotSupportedException("SwitchToFlashAppContext: Error 0x" + Error.ToString("X4")); + } + } + + DisableRebootTimeOut(); + + Info.App = FlashAppType.FlashApp; + + // If current Info class was retrieved while in BootMgr mode, then we need to invalidate this data, because it is incomplete. + if (Info.PlatformID == null) + { + Info.State = PhoneInfoState.Empty; + } + + InterfaceChanged(PhoneInterfaces.Lumia_Flash); + } + + internal void SwitchToBootManagerContext(bool DisableTimeOut = true) + { + byte[] Request = new byte[7]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXCBB"); + byte[] Response = ExecuteRawMethod(Request); + if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU") + { + throw new NotSupportedException(); + } + + UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]); + if (Error > 0) + { + throw new NotSupportedException("SwitchToBootManagerContext: Error 0x" + Error.ToString("X4")); + } + + if (DisableTimeOut) + { + DisableRebootTimeOut(); + } + + Info.App = FlashAppType.BootManager; + InterfaceChanged(PhoneInterfaces.Lumia_Bootloader); + } + + internal string ReadPhoneInfoVariable(string VariableName) + { + // This function assumes the phone is in Phone Info App context + + byte[] Request = new byte[16]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXPH" + VariableName + "\0"); // BTR or CTR, CTR is public ProductCode + byte[] Response = ExecuteRawMethod(Request); + UInt16 Length = BigEndian.ToUInt16(Response, 6); + return ByteOperations.ReadAsciiString(Response, 8, Length).Trim(new char[] { ' ', '\0' }); + } + + internal string ReadProductCode() + { + SwitchToPhoneInfoAppContext(); + string Result = ReadPhoneInfoVariable("CTR"); + SwitchToFlashAppContext(); + return Result; + } + + internal void StartAsyncFlash() + { + byte[] Request = new byte[14]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXFFS"); + Request[8] = 1; // Protocol version must be 1 + Request[9] = 0; // Protocol type must be 0 + ExecuteRawMethod(Request); + } + + internal void EndAsyncFlash() + { + byte[] Request = new byte[7]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXFFE"); + ExecuteRawMethod(Request); + } + + internal enum SecureBootKeyType : byte + { + Retail = 0, + Engineering = 1 + } + + internal void ProvisionSecureBootKeys(SecureBootKeyType KeyType) // Only for Flashmode, not BootManager mode. + { + byte[] Request = new byte[8]; + ByteOperations.WriteAsciiString(Request, 0, "NOKXFK"); + Request[6] = 0; // Options + Request[7] = (byte)KeyType; + byte[] Response = ExecuteRawMethod(Request); + UInt32 Status = ByteOperations.ReadUInt32(Response, 6); + if (Status != 0) + { + ThrowFlashError((int)Status); + } + } + } + + internal enum FlashAppType + { + BootManager = 1, + FlashApp = 2, + PhoneInfoApp = 3 + }; + + internal enum PhoneInfoState + { + Empty, + Basic, + Extended + }; + + internal class PhoneInfo + { + public PhoneInfoState State = PhoneInfoState.Empty; + + public string Type; // Extended info + public string ProductCode; // Extended info + public string Imei; // Extended info + public string Firmware; // Extended info + public byte[] RKH; // Extended info + + public FlashAppType App; + + public byte FlashAppVersionMajor; + public byte FlashAppVersionMinor; + public byte FlashAppProtocolVersionMajor; + public byte FlashAppProtocolVersionMinor; + + public byte BootManagerVersionMajor; + public byte BootManagerVersionMinor; + public byte BootManagerProtocolVersionMajor; + public byte BootManagerProtocolVersionMinor; + + public byte PhoneInfoAppVersionMajor; + public byte PhoneInfoAppVersionMinor; + public byte PhoneInfoAppProtocolVersionMajor; + public byte PhoneInfoAppProtocolVersionMinor; + + public UInt32 TransferSize; + public bool MmosOverUsbSupported; + public UInt32 SdCardSizeInSectors; + public UInt32 WriteBufferSize; + public UInt32 EmmcSizeInSectors; + public string PlatformID; + public UInt16 SecureFfuSupportedProtocolMask; + public bool AsyncSupport; + + public bool PlatformSecureBootEnabled; + public bool SecureFfuEnabled; + public bool JtagDisabled; + public bool RdcPresent; + public bool Authenticated; + public bool UefiSecureBootEnabled; + public bool SecondaryHardwareKeyPresent; + + public bool IsBootloaderSecure; + + internal void Log(LogType Type) + { + if (State == PhoneInfoState.Extended) + { + if (this.Type != null) + { + LogFile.Log("Phone type: " + this.Type, Type); + } + + if (ProductCode != null) + { + LogFile.Log("Product code: " + ProductCode, Type); + } + + if (RKH != null) + { + LogFile.Log("Root key hash: " + Converter.ConvertHexToString(RKH, ""), Type); + } + + if (Firmware?.Length > 0) + { + LogFile.Log("Firmware version: " + Firmware, Type); + } + + if (Type != LogType.ConsoleOnly && (Imei != null)) + { + LogFile.Log("IMEI: " + Imei, LogType.FileOnly); + } + } + + switch (App) + { + case FlashAppType.BootManager: + LogFile.Log("Bootmanager: " + BootManagerVersionMajor + "." + BootManagerVersionMinor, Type); + LogFile.Log("Bootmanager protocol: " + BootManagerProtocolVersionMajor + "." + BootManagerProtocolVersionMinor, Type); + LogFile.Log("Flash app: " + FlashAppVersionMajor + "." + FlashAppVersionMinor, Type); + LogFile.Log("Flash protocol: " + FlashAppProtocolVersionMajor + "." + FlashAppProtocolVersionMinor, Type); + break; + case FlashAppType.FlashApp: + LogFile.Log("Flash app: " + FlashAppVersionMajor + "." + FlashAppVersionMinor, Type); + LogFile.Log("Flash protocol: " + FlashAppProtocolVersionMajor + "." + FlashAppProtocolVersionMinor, Type); + break; + case FlashAppType.PhoneInfoApp: + LogFile.Log("Phone info app: " + PhoneInfoAppVersionMajor + "." + PhoneInfoAppVersionMinor, Type); + LogFile.Log("Phone info protocol: " + PhoneInfoAppProtocolVersionMajor + "." + PhoneInfoAppProtocolVersionMinor, Type); + break; + } + + LogFile.Log("SecureBoot: " + ((!PlatformSecureBootEnabled || !UefiSecureBootEnabled) ? "Disabled" : "Enabled") + " (Platform Secure Boot: " + (PlatformSecureBootEnabled ? "Enabled" : "Disabled") + ", UEFI Secure Boot: " + (UefiSecureBootEnabled ? "Enabled" : "Disabled") + ")", Type); + + if ((Type == LogType.ConsoleOnly) || (Type == LogType.FileAndConsole)) + { + LogFile.Log("Flash app security: " + (!IsBootloaderSecure ? "Disabled" : "Enabled"), LogType.ConsoleOnly); + } + + if ((Type == LogType.FileOnly) || (Type == LogType.FileAndConsole)) + { + LogFile.Log("Flash app security: " + (!IsBootloaderSecure ? "Disabled" : "Enabled") + " (FFU security: " + (SecureFfuEnabled ? "Enabled" : "Disabled") + ", RDC: " + (RdcPresent ? "Present" : "Not found") + ", Authenticated: " + (Authenticated ? "True" : "False") + ")", LogType.FileOnly); + } + + LogFile.Log("JTAG: " + (JtagDisabled ? "Disabled" : "Enabled"), Type); + } + } + + internal class UefiSecurityStatusResponse + { + public byte IsTestDevice; + public bool PlatformSecureBootStatus; + public bool SecureFfuEfuseStatus; + public bool DebugStatus; + public bool RdcStatus; + public bool AuthenticationStatus; + public bool UefiSecureBootStatus; + public bool CryptoHardwareKey; + } + + internal class FlashVersion + { + public int ApplicationMajor; + public int ApplicationMinor; + public int ProtocolMajor; + public int ProtocolMinor; + } +} diff --git a/Models/NokiaPhoneModel.cs b/WPinternals/Models/NokiaPhoneModel.cs similarity index 97% rename from Models/NokiaPhoneModel.cs rename to WPinternals/Models/NokiaPhoneModel.cs index bea83eb..be2851d 100644 --- a/Models/NokiaPhoneModel.cs +++ b/WPinternals/Models/NokiaPhoneModel.cs @@ -1,412 +1,412 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using MadWizard.WinUSBNet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; - -namespace WPinternals -{ - internal class NokiaPhoneModel : IDisposable - { - protected bool Disposed = false; - private readonly USBDevice Device = null; - private int MessageId = 0; - private readonly object UsbLock = new(); - - public NokiaPhoneModel(string DevicePath) - { - // Mass Storage device is not WinUSB - try - { - Device = new USBDevice(DevicePath); - } - catch { } - } - - private JsonElement? ExecuteJsonMethodAsJsonToken(string JsonMethod, Dictionary Params, string ResultElement) - { - byte[] Buffer; - int Length; - - lock (UsbLock) - { - const string jsonrpc = "2.0"; - int id = MessageId++; - string method = JsonMethod; - Dictionary @params = new(); - if (Params != null) - { - foreach (KeyValuePair Param in Params) - { - if (Param.Value is byte[] v) - { - @params.Add(Param.Key, v.Select(b => (int)b).ToArray()); // convert to int-array - } - else - { - @params.Add(Param.Key, Param.Value); - } - } - } - @params.Add("MessageVersion", 0); - string Request = JsonSerializer.Serialize(new { jsonrpc, id, method, @params }); - Device.OutputPipe.Write(System.Text.Encoding.ASCII.GetBytes(Request)); - - Buffer = new byte[0x10000]; - Length = Device.InputPipe.Read(Buffer); - } - - JsonDocument ResultMessage = JsonDocument.Parse(System.Text.Encoding.ASCII.GetString(Buffer, 0, Length)); - - try - { - JsonElement? ResultToken = ResultMessage.RootElement.GetProperty("result"); - if ((ResultToken == null) || (ResultElement == null)) - { - return null; - } - - return ResultToken.Value.GetProperty(ResultElement); - } - catch - { - return null; - } - } - - public void ExecuteJsonMethod(string JsonMethod, Dictionary Params) - { - _ = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, null); - } - - public string ExecuteJsonMethodAsString(string JsonMethod, Dictionary Params, string ResultElement) - { - JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, ResultElement); - if (Token == null) - { - return null; - } - - return Token.Value.GetString(); - } - - public string ExecuteJsonMethodAsString(string JsonMethod, string ResultElement) - { - JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); - if (Token == null) - { - return null; - } - - return Token.Value.GetString(); - } - - public int ExecuteJsonMethodAsInteger(string JsonMethod, Dictionary Params, string ResultElement) - { - JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, ResultElement); - if (Token == null) - { - return 0; - } - - return Token.Value.GetInt32(); - } - - public int ExecuteJsonMethodAsInteger(string JsonMethod, string ResultElement) - { - JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); - if (Token == null) - { - return 0; - } - - return Token.Value.GetInt32(); - } - - public byte[] ExecuteJsonMethodAsBytes(string JsonMethod, Dictionary Params, string ResultElement) - { - JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, ResultElement); - if (Token == null) - { - return null; - } - - return Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray(); - } - - public byte[] ExecuteJsonMethodAsBytes(string JsonMethod, string ResultElement) - { - JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); - if (Token == null) - { - return null; - } - - return Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray(); - } - - public bool? ExecuteJsonMethodAsBoolean(string JsonMethod, string ResultElement) - { - JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); - if (Token == null) - { - return null; - } - - return Token.Value.GetBoolean(); - } - - public void ExecuteJsonMethodAsync(string JsonMethod, Dictionary Params) - { - lock (UsbLock) - { - const string jsonrpc = "2.0"; - int id = MessageId++; - string method = JsonMethod; - Dictionary @params = new(); - if (Params != null) - { - foreach (KeyValuePair Param in Params) - { - if (Param.Value is byte[] v) - { - @params.Add(Param.Key, v.Select(b => (int)b).ToArray()); // convert to int-array - } - else - { - @params.Add(Param.Key, Param.Value); - } - } - } - @params.Add("MessageVersion", 0); - string Request = JsonSerializer.Serialize(new { jsonrpc, id, method, @params }); - - byte[] OutBuffer = System.Text.Encoding.ASCII.GetBytes(Request); - Device.OutputPipe.BeginWrite(OutBuffer, 0, OutBuffer.Length, (AsyncResultWrite) => Device.OutputPipe.EndWrite(AsyncResultWrite), null); - } - } - - public void ExecuteJsonMethodAsync(string JsonMethod) - { - ExecuteJsonMethod(JsonMethod, null); - } - - public delegate void JsonMethodCallbackString(object State, string Result); - - public void ExecuteJsonMethodAsStringAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackString Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetRawText())); - } - - public void ExecuteJsonMethodAsStringAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackString Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetRawText())); - } - - public delegate void JsonMethodCallbackBoolean(object State, bool Result); - - public void ExecuteJsonMethodAsBooleanAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackBoolean Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetBoolean())); - } - - public void ExecuteJsonMethodAsBooleanAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackBoolean Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetBoolean())); - } - - public delegate void JsonMethodCallbackBytes(object State, byte[] Result); - - public void ExecuteJsonMethodAsBytesAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackBytes Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray())); - } - - public void ExecuteJsonMethodAsBytesAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackBytes Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray())); - } - - public delegate void JsonMethodCallbackInteger(object State, int Result); - - public void ExecuteJsonMethodAsIntegerAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackInteger Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetInt32())); - } - - public void ExecuteJsonMethodAsIntegerAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackInteger Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetInt32())); - } - - public delegate void JsonMethodCallbackToken(object State, JsonElement? Result); - - public void ExecuteJsonMethodAsTokenAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackToken Callback) - { - byte[] Buffer; - int Length; - - lock (UsbLock) - { - const string jsonrpc = "2.0"; - int id = MessageId++; - string method = JsonMethod; - Dictionary @params = new(); - if (Params != null) - { - foreach (KeyValuePair Param in Params) - { - if (Param.Value is byte[] v) - { - @params.Add(Param.Key, v.Select(b => (int)b).ToArray()); // convert to int-array - } - else - { - @params.Add(Param.Key, Param.Value); - } - } - } - @params.Add("MessageVersion", 0); - string Request = JsonSerializer.Serialize(new { jsonrpc, id, method, @params }); - - byte[] OutBuffer = System.Text.Encoding.ASCII.GetBytes(Request); - Device.OutputPipe.BeginWrite(OutBuffer, 0, OutBuffer.Length, (AsyncResultWrite) => - { - Device.OutputPipe.EndWrite(AsyncResultWrite); - Buffer = new byte[0x10000]; - Device.InputPipe.BeginRead(Buffer, 0, 0x10000, (AsyncResultRead) => - { - Length = Device.InputPipe.EndRead(AsyncResultRead); - - JsonDocument ResultMessage = JsonDocument.Parse(System.Text.Encoding.ASCII.GetString(Buffer, 0, Length)); - - JsonElement? ResultToken = ResultMessage.RootElement.GetProperty("result"); - if ((ResultToken == null) || (ResultElement == null)) - { - Callback(AsyncResultRead.AsyncState, null); - } - - Callback(AsyncResultRead.AsyncState, ResultToken.Value.GetProperty(ResultElement)); - }, AsyncResultWrite.AsyncState); - }, State); - } - } - - public void ExecuteJsonMethodAsTokenAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackToken Callback) - { - ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, Callback); - } - - public byte[] ExecuteRawMethod(byte[] RawMethod) - { - return ExecuteRawMethod(RawMethod, RawMethod.Length); - } - - public byte[] ExecuteRawMethod(byte[] RawMethod, int Length) - { - byte[] Buffer = new byte[0x8000]; // Should be at least 0x4408 for receiving the GPT packet. - byte[] Result = null; - lock (UsbLock) - { - Device.OutputPipe.Write(RawMethod, 0, Length); - try - { - int OutputLength = Device.InputPipe.Read(Buffer); - Result = new byte[OutputLength]; - System.Buffer.BlockCopy(Buffer, 0, Result, 0, OutputLength); - } - catch { } // Reboot command looses connection - } - return Result; - } - - public void ExecuteRawVoidMethod(byte[] RawMethod) - { - ExecuteRawVoidMethod(RawMethod, RawMethod.Length); - } - - public void ExecuteRawVoidMethod(byte[] RawMethod, int Length) - { - lock (UsbLock) - { - Device.OutputPipe.Write(RawMethod, 0, Length); - } - } - - public void ResetDevice() - { - try - { - foreach (var pipe in Device.Pipes) - { - pipe.Abort(); - pipe.Reset(); - } - } - catch { } - } - - /// - /// Disposes the UsbDevice including all unmanaged WinUSB handles. This function - /// should be called when the UsbDevice object is no longer in use, otherwise - /// unmanaged handles will remain open until the garbage collector finalizes the - /// object. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Finalizer for the UsbDevice. Disposes all unmanaged handles. - /// - ~NokiaPhoneModel() - { - Dispose(false); - } - - /// - /// Disposes the object - /// - /// Indicates wether Dispose was called manually (true) or by - /// the garbage collector (false) via the destructor. - protected virtual void Dispose(bool disposing) - { - if (Disposed) - { - return; - } - - if (disposing) - { - Device?.Dispose(); - } - - // Clean unmanaged resources here. - // (none currently) - - Disposed = true; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using MadWizard.WinUSBNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace WPinternals +{ + internal class NokiaPhoneModel : IDisposable + { + protected bool Disposed = false; + private readonly USBDevice Device = null; + private int MessageId = 0; + private readonly object UsbLock = new(); + + public NokiaPhoneModel(string DevicePath) + { + // Mass Storage device is not WinUSB + try + { + Device = new USBDevice(DevicePath); + } + catch { } + } + + private JsonElement? ExecuteJsonMethodAsJsonToken(string JsonMethod, Dictionary Params, string ResultElement) + { + byte[] Buffer; + int Length; + + lock (UsbLock) + { + const string jsonrpc = "2.0"; + int id = MessageId++; + string method = JsonMethod; + Dictionary @params = new(); + if (Params != null) + { + foreach (KeyValuePair Param in Params) + { + if (Param.Value is byte[] v) + { + @params.Add(Param.Key, v.Select(b => (int)b).ToArray()); // convert to int-array + } + else + { + @params.Add(Param.Key, Param.Value); + } + } + } + @params.Add("MessageVersion", 0); + string Request = JsonSerializer.Serialize(new { jsonrpc, id, method, @params }); + Device.OutputPipe.Write(System.Text.Encoding.ASCII.GetBytes(Request)); + + Buffer = new byte[0x10000]; + Length = Device.InputPipe.Read(Buffer); + } + + JsonDocument ResultMessage = JsonDocument.Parse(System.Text.Encoding.ASCII.GetString(Buffer, 0, Length)); + + try + { + JsonElement? ResultToken = ResultMessage.RootElement.GetProperty("result"); + if ((ResultToken == null) || (ResultElement == null)) + { + return null; + } + + return ResultToken.Value.GetProperty(ResultElement); + } + catch + { + return null; + } + } + + public void ExecuteJsonMethod(string JsonMethod, Dictionary Params) + { + _ = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, null); + } + + public string ExecuteJsonMethodAsString(string JsonMethod, Dictionary Params, string ResultElement) + { + JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, ResultElement); + if (Token == null) + { + return null; + } + + return Token.Value.GetString(); + } + + public string ExecuteJsonMethodAsString(string JsonMethod, string ResultElement) + { + JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); + if (Token == null) + { + return null; + } + + return Token.Value.GetString(); + } + + public int ExecuteJsonMethodAsInteger(string JsonMethod, Dictionary Params, string ResultElement) + { + JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, ResultElement); + if (Token == null) + { + return 0; + } + + return Token.Value.GetInt32(); + } + + public int ExecuteJsonMethodAsInteger(string JsonMethod, string ResultElement) + { + JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); + if (Token == null) + { + return 0; + } + + return Token.Value.GetInt32(); + } + + public byte[] ExecuteJsonMethodAsBytes(string JsonMethod, Dictionary Params, string ResultElement) + { + JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, Params, ResultElement); + if (Token == null) + { + return null; + } + + return Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray(); + } + + public byte[] ExecuteJsonMethodAsBytes(string JsonMethod, string ResultElement) + { + JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); + if (Token == null) + { + return null; + } + + return Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray(); + } + + public bool? ExecuteJsonMethodAsBoolean(string JsonMethod, string ResultElement) + { + JsonElement? Token = ExecuteJsonMethodAsJsonToken(JsonMethod, null, ResultElement); + if (Token == null) + { + return null; + } + + return Token.Value.GetBoolean(); + } + + public void ExecuteJsonMethodAsync(string JsonMethod, Dictionary Params) + { + lock (UsbLock) + { + const string jsonrpc = "2.0"; + int id = MessageId++; + string method = JsonMethod; + Dictionary @params = new(); + if (Params != null) + { + foreach (KeyValuePair Param in Params) + { + if (Param.Value is byte[] v) + { + @params.Add(Param.Key, v.Select(b => (int)b).ToArray()); // convert to int-array + } + else + { + @params.Add(Param.Key, Param.Value); + } + } + } + @params.Add("MessageVersion", 0); + string Request = JsonSerializer.Serialize(new { jsonrpc, id, method, @params }); + + byte[] OutBuffer = System.Text.Encoding.ASCII.GetBytes(Request); + Device.OutputPipe.BeginWrite(OutBuffer, 0, OutBuffer.Length, (AsyncResultWrite) => Device.OutputPipe.EndWrite(AsyncResultWrite), null); + } + } + + public void ExecuteJsonMethodAsync(string JsonMethod) + { + ExecuteJsonMethod(JsonMethod, null); + } + + public delegate void JsonMethodCallbackString(object State, string Result); + + public void ExecuteJsonMethodAsStringAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackString Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetRawText())); + } + + public void ExecuteJsonMethodAsStringAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackString Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetRawText())); + } + + public delegate void JsonMethodCallbackBoolean(object State, bool Result); + + public void ExecuteJsonMethodAsBooleanAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackBoolean Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetBoolean())); + } + + public void ExecuteJsonMethodAsBooleanAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackBoolean Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetBoolean())); + } + + public delegate void JsonMethodCallbackBytes(object State, byte[] Result); + + public void ExecuteJsonMethodAsBytesAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackBytes Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray())); + } + + public void ExecuteJsonMethodAsBytesAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackBytes Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.EnumerateArray().Select(x => x.GetByte()).ToArray())); + } + + public delegate void JsonMethodCallbackInteger(object State, int Result); + + public void ExecuteJsonMethodAsIntegerAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackInteger Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, Params, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetInt32())); + } + + public void ExecuteJsonMethodAsIntegerAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackInteger Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, (ReturnState, Token) => Callback(ReturnState, Token.Value.GetInt32())); + } + + public delegate void JsonMethodCallbackToken(object State, JsonElement? Result); + + public void ExecuteJsonMethodAsTokenAsync(string JsonMethod, Dictionary Params, string ResultElement, object State, JsonMethodCallbackToken Callback) + { + byte[] Buffer; + int Length; + + lock (UsbLock) + { + const string jsonrpc = "2.0"; + int id = MessageId++; + string method = JsonMethod; + Dictionary @params = new(); + if (Params != null) + { + foreach (KeyValuePair Param in Params) + { + if (Param.Value is byte[] v) + { + @params.Add(Param.Key, v.Select(b => (int)b).ToArray()); // convert to int-array + } + else + { + @params.Add(Param.Key, Param.Value); + } + } + } + @params.Add("MessageVersion", 0); + string Request = JsonSerializer.Serialize(new { jsonrpc, id, method, @params }); + + byte[] OutBuffer = System.Text.Encoding.ASCII.GetBytes(Request); + Device.OutputPipe.BeginWrite(OutBuffer, 0, OutBuffer.Length, (AsyncResultWrite) => + { + Device.OutputPipe.EndWrite(AsyncResultWrite); + Buffer = new byte[0x10000]; + Device.InputPipe.BeginRead(Buffer, 0, 0x10000, (AsyncResultRead) => + { + Length = Device.InputPipe.EndRead(AsyncResultRead); + + JsonDocument ResultMessage = JsonDocument.Parse(System.Text.Encoding.ASCII.GetString(Buffer, 0, Length)); + + JsonElement? ResultToken = ResultMessage.RootElement.GetProperty("result"); + if ((ResultToken == null) || (ResultElement == null)) + { + Callback(AsyncResultRead.AsyncState, null); + } + + Callback(AsyncResultRead.AsyncState, ResultToken.Value.GetProperty(ResultElement)); + }, AsyncResultWrite.AsyncState); + }, State); + } + } + + public void ExecuteJsonMethodAsTokenAsync(string JsonMethod, string ResultElement, object State, JsonMethodCallbackToken Callback) + { + ExecuteJsonMethodAsTokenAsync(JsonMethod, null, ResultElement, State, Callback); + } + + public byte[] ExecuteRawMethod(byte[] RawMethod) + { + return ExecuteRawMethod(RawMethod, RawMethod.Length); + } + + public byte[] ExecuteRawMethod(byte[] RawMethod, int Length) + { + byte[] Buffer = new byte[0x8000]; // Should be at least 0x4408 for receiving the GPT packet. + byte[] Result = null; + lock (UsbLock) + { + Device.OutputPipe.Write(RawMethod, 0, Length); + try + { + int OutputLength = Device.InputPipe.Read(Buffer); + Result = new byte[OutputLength]; + System.Buffer.BlockCopy(Buffer, 0, Result, 0, OutputLength); + } + catch { } // Reboot command looses connection + } + return Result; + } + + public void ExecuteRawVoidMethod(byte[] RawMethod) + { + ExecuteRawVoidMethod(RawMethod, RawMethod.Length); + } + + public void ExecuteRawVoidMethod(byte[] RawMethod, int Length) + { + lock (UsbLock) + { + Device.OutputPipe.Write(RawMethod, 0, Length); + } + } + + public void ResetDevice() + { + try + { + foreach (var pipe in Device.Pipes) + { + pipe.Abort(); + pipe.Reset(); + } + } + catch { } + } + + /// + /// Disposes the UsbDevice including all unmanaged WinUSB handles. This function + /// should be called when the UsbDevice object is no longer in use, otherwise + /// unmanaged handles will remain open until the garbage collector finalizes the + /// object. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Finalizer for the UsbDevice. Disposes all unmanaged handles. + /// + ~NokiaPhoneModel() + { + Dispose(false); + } + + /// + /// Disposes the object + /// + /// Indicates wether Dispose was called manually (true) or by + /// the garbage collector (false) via the destructor. + protected virtual void Dispose(bool disposing) + { + if (Disposed) + { + return; + } + + if (disposing) + { + Device?.Dispose(); + } + + // Clean unmanaged resources here. + // (none currently) + + Disposed = true; + } + } +} diff --git a/Models/PatchEngine.cs b/WPinternals/Models/PatchEngine.cs similarity index 97% rename from Models/PatchEngine.cs rename to WPinternals/Models/PatchEngine.cs index a8e7f44..3fe56a0 100644 --- a/Models/PatchEngine.cs +++ b/WPinternals/Models/PatchEngine.cs @@ -1,609 +1,609 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.AccessControl; -using System.Security.Cryptography; -using System.Security.Principal; -using System.Xml; -using System.Xml.Serialization; - -namespace WPinternals -{ - internal class PatchEngine - { - internal List PatchDefinitions = new(); - internal readonly List TargetRedirections = new(); - - internal PatchEngine() { } - - internal PatchEngine(string PatchDefinitionsXmlString) - { - XmlSerializer x = new(PatchDefinitions.GetType(), null, Array.Empty(), new XmlRootAttribute("PatchDefinitions"), ""); - MemoryStream s = new(System.Text.Encoding.ASCII.GetBytes(PatchDefinitionsXmlString)); - PatchDefinitions = (List)x.Deserialize(s); - } - - internal void WriteDefinitions(string FilePath) - { - XmlSerializer x = new(PatchDefinitions.GetType(), null, Array.Empty(), new XmlRootAttribute("PatchDefinitions"), ""); - - XmlSerializerNamespaces ns = new(); - ns.Add("", ""); - - StreamWriter FileWriter = new(FilePath); - XmlWriter XmlWriter = XmlWriter.Create(FileWriter, new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true, NewLineHandling = NewLineHandling.Entitize }); - - FileWriter.WriteLine(""); - FileWriter.WriteLine(""); - FileWriter.WriteLine(""); - FileWriter.WriteLine(""); - - x.Serialize(XmlWriter, PatchDefinitions, ns); - - FileWriter.Close(); - } - - private string _TargetPath = null; - internal string TargetPath - { - get - { - return _TargetPath; - } - set - { - _TargetPath = value.TrimEnd(new char[] { '\\' }); - } - } - - private DiscUtils.DiscFileSystem _TargetImage = null; - internal DiscUtils.DiscFileSystem TargetImage - { - get - { - return _TargetImage; - } - set - { - _TargetImage = value; - _TargetPath = ""; - } - } - - internal bool Patch(string PatchDefinition) - { - bool Result = false; - List LoadedFiles = new(); - - LogFile.Log("Attempt patch: " + PatchDefinition); - - // Find a matching TargetVersion - PatchDefinition Definition = PatchDefinitions.Single(d => string.Equals(d.Name, PatchDefinition, StringComparison.CurrentCultureIgnoreCase)); - TargetVersion MatchedVersion = null; - int VersionIndex = 0; - foreach (TargetVersion CurrentVersion in Definition.TargetVersions) - { - bool Match = true; - int FileIndex = 0; - - foreach (TargetFile CurrentTargetFile in CurrentVersion.TargetFiles) - { - // Determine target path - string TargetPath = null; - foreach (TargetRedirection CurrentRedirection in TargetRedirections) - { - if (CurrentTargetFile.Path.StartsWith(CurrentRedirection.RelativePath, StringComparison.OrdinalIgnoreCase)) - { - TargetPath = Path.Combine(CurrentRedirection.TargetPath + "\\", CurrentTargetFile.Path); - break; - } - } - if (TargetPath == null) - { - TargetPath = Path.Combine(this.TargetPath + "\\", CurrentTargetFile.Path); - } - - // Lookup file - FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, TargetPath, StringComparison.CurrentCultureIgnoreCase)); - if (CurrentFile == null) - { - CurrentFile = (TargetImage != null) && (!TargetPath.Contains(':')) - ? new FilePatcher(TargetPath, TargetImage.OpenFile(TargetPath, FileMode.Open, FileAccess.ReadWrite)) - : new FilePatcher(TargetPath); - - LoadedFiles.Add(CurrentFile); - } - - // Compare hash - if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal) && - !StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) - { - Match = false; - - foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) - { - if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) - { - Match = true; // Found match after all. File is patched with an obsolete version of this patch. - break; - } - } - - if (!Match) - { - LogFile.Log("Pattern: " + VersionIndex.ToString() + ", " + FileIndex.ToString()); - break; - } - } - - FileIndex++; - } - - if (Match) - { - MatchedVersion = CurrentVersion; - break; - } - - VersionIndex++; - } - - if (MatchedVersion != null) - { - LogFile.Log("Apply: " + MatchedVersion.Description); - - foreach (TargetFile CurrentTargetFile in MatchedVersion.TargetFiles) - { - FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, Path.Combine(TargetPath + "\\", CurrentTargetFile.Path), StringComparison.CurrentCultureIgnoreCase)); - - if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) - { - CurrentFile.StartPatching(); - - if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal)) - { - // File is already patched, but with an older version of this patch. - // First unpatch back to original. - - foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) - { - if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) - { - foreach (Patch CurrentPatch in CurrentObsoleteFile.Patches) - { - CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes); - } - break; - } - } - } - - foreach (Patch CurrentPatch in CurrentTargetFile.Patches) - { - CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.PatchedBytes); - } - - CurrentFile.FinishPatching(); - } - } - - Result = true; - } - - return Result; - } - - internal void Restore(string PatchDefinition) - { - List LoadedFiles = new(); - - try - { - // Find a matching TargetVersion - PatchDefinition Definition = PatchDefinitions.Single(d => string.Equals(d.Name, PatchDefinition, StringComparison.CurrentCultureIgnoreCase)); - TargetVersion MatchedVersion = null; - foreach (TargetVersion CurrentVersion in Definition.TargetVersions) - { - bool Match = true; - - foreach (TargetFile CurrentTargetFile in CurrentVersion.TargetFiles) - { - // Determine target path - string TargetPath = null; - foreach (TargetRedirection CurrentRedirection in TargetRedirections) - { - if (CurrentTargetFile.Path.StartsWith(CurrentRedirection.RelativePath, StringComparison.OrdinalIgnoreCase)) - { - TargetPath = Path.Combine(CurrentRedirection.TargetPath, CurrentTargetFile.Path); - break; - } - } - if (TargetPath == null) - { - TargetPath = Path.Combine(this.TargetPath, CurrentTargetFile.Path); - } - - // Lookup file - FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, TargetPath, StringComparison.CurrentCultureIgnoreCase)); - if (CurrentFile == null) - { - CurrentFile = new FilePatcher(TargetPath); - LoadedFiles.Add(CurrentFile); - } - - // Compare hash - if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal) && - !StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) - { - Match = false; - - foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) - { - if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) - { - Match = true; // Found match after all. File is patched with an obsolete version of this patch. - break; - } - } - - if (!Match) - { - break; - } - } - } - - if (Match) - { - MatchedVersion = CurrentVersion; - break; - } - } - - if (MatchedVersion != null) - { - foreach (TargetFile CurrentTargetFile in MatchedVersion.TargetFiles) - { - FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, Path.Combine(TargetPath, CurrentTargetFile.Path), StringComparison.CurrentCultureIgnoreCase)); - - if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal)) - { - CurrentFile.StartPatching(); - - if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) - { - foreach (Patch CurrentPatch in CurrentTargetFile.Patches) - { - CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes); - } - } - else - { - foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) - { - if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) - { - foreach (Patch CurrentPatch in CurrentObsoleteFile.Patches) - { - CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes); - } - break; - } - } - } - - CurrentFile.FinishPatching(); - } - } - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - } - } - - internal class TargetRedirection - { - private string _RelativePath; - private string _TargetPath; - - internal TargetRedirection(string RelativePath, string TargetPath) - { - this.RelativePath = RelativePath; - this.TargetPath = TargetPath; - } - - internal string RelativePath - { - get - { - return _RelativePath; - } - set - { - _RelativePath = value.TrimStart(new char[] { '\\' }).TrimEnd(new char[] { '\\' }); - } - } - - internal string TargetPath - { - get - { - return _TargetPath; - } - set - { - _TargetPath = value.TrimEnd(new char[] { '\\' }); - } - } - } - - internal class FilePatcher - { - internal byte[] Hash = null; - internal string FilePath; - internal FileSecurity OriginalACL; - internal Privilege TakeOwnershipPrivilege; - internal Privilege RestorePrivilege; - internal Stream Stream = null; - - internal FilePatcher(string FilePath) - { - this.FilePath = FilePath; - using FileStream stream = File.OpenRead(FilePath); - SHA1Managed sha = new(); - Hash = sha.ComputeHash(stream); - } - - internal FilePatcher(string FilePath, Stream FileStream) - { - if (!FileStream.CanSeek || !FileStream.CanWrite) - { - throw new WPinternalsException("Incorrect filestream", "The provided file stream for patching does not support seeking and/or writing."); - } - - this.FilePath = FilePath; - this.Stream = FileStream; - FileStream.Position = 0; - SHA1Managed sha = new(); - Hash = sha.ComputeHash(FileStream); - FileStream.Position = 0; - } - - internal void StartPatching() - { - if (FilePath.Contains(':')) - { - FileInfo fileInfo = new(FilePath); - - // Enable Take Ownership AND Restore ownership to original owner - // Take Ownership Privilge is not enough. - // We need Restore Privilege. - RestorePrivilege = new Privilege(Privilege.Restore); - RestorePrivilege.Enable(); - - if ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor <= 1)) - { - // On Vista or 7 - TakeOwnershipPrivilege = new Privilege(Privilege.TakeOwnership); - TakeOwnershipPrivilege.Enable(); - } - - // Backup original owner and ACL - OriginalACL = fileInfo.GetAccessControl(); - - // And take the original security to create new security rules. - FileSecurity NewACL = fileInfo.GetAccessControl(); - - // Take ownership - NewACL.SetOwner(WindowsIdentity.GetCurrent().User); - fileInfo.SetAccessControl(NewACL); - - // And create a new access rule - NewACL.SetAccessRule(new FileSystemAccessRule(WindowsIdentity.GetCurrent().User, FileSystemRights.FullControl, AccessControlType.Allow)); - fileInfo.SetAccessControl(NewACL); - - // Open the file for patching - Stream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite); - } - } - - internal void ApplyPatch(UInt32 Offset, byte[] Bytes) - { - Stream.Position = Offset; - Stream.Write(Bytes, 0, Bytes.Length); - } - - internal void FinishPatching() - { - // Close file - Stream.Close(); - - if (FilePath.Contains(':')) - { - FileInfo fileInfo = new(FilePath); - - // Restore original owner and access rules. - // The OriginalACL cannot be reused directly. - FileSecurity NewACL = fileInfo.GetAccessControl(); - NewACL.SetSecurityDescriptorBinaryForm(OriginalACL.GetSecurityDescriptorBinaryForm()); - fileInfo.SetAccessControl(NewACL); - - // Revert to self - RestorePrivilege.Revert(); - RestorePrivilege.Disable(); - - if ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor <= 1)) - { - // On Vista or 7 - TakeOwnershipPrivilege.Revert(); - TakeOwnershipPrivilege.Disable(); - } - } - } - } - - public class PatchDefinition // Must be public to be serializable - { - [XmlAttribute] - public string Name; - - public List TargetVersions = new(); - } - - public class TargetVersion // Must be public to be serializable - { - [XmlAttribute] - public string Description; - - public List TargetFiles = new(); - } - - public class TargetFile // Must be public to be serializable - { - private string _Path; - [XmlAttribute] - public string Path - { - get - { - return _Path; - } - set - { - _Path = value.TrimStart(new char[] { '\\' }); - } - } - - [XmlIgnore] - public byte[] HashOriginal; - [XmlAttribute("HashOriginal")] - public string HashOriginalAsString - { - get - { - return Converter.ConvertHexToString(HashOriginal, ""); - } - set - { - HashOriginal = Converter.ConvertStringToHex(value); - } - } - - [XmlIgnore] - public byte[] HashPatched; - [XmlAttribute("HashPatched")] - public string HashPatchedAsString - { - get - { - return Converter.ConvertHexToString(HashPatched, ""); - } - set - { - HashPatched = Converter.ConvertStringToHex(value); - } - } - - public List Patches = new(); - public List Obsolete = new(); - } - - public class Patch // Must be public to be serializable - { - [XmlIgnore] - public UInt32 Address; - [XmlAttribute("Address")] - public string AddressAsString - { - get - { - return "0x" + Address.ToString("X8"); - } - set - { - string NewValue = value; - if (NewValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - { - NewValue = NewValue[2..]; - } - - Address = Convert.ToUInt32(NewValue, 16); - } - } - - [XmlIgnore] - public byte[] OriginalBytes; - [XmlAttribute("OriginalBytes")] - public string OriginalBytesAsString - { - get - { - return Converter.ConvertHexToString(OriginalBytes, ""); - } - set - { - OriginalBytes = Converter.ConvertStringToHex(value); - } - } - - [XmlIgnore] - public byte[] PatchedBytes; - [XmlAttribute("PatchedBytes")] - public string PatchedBytesAsString - { - get - { - return Converter.ConvertHexToString(PatchedBytes, ""); - } - set - { - PatchedBytes = Converter.ConvertStringToHex(value); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.AccessControl; +using System.Security.Cryptography; +using System.Security.Principal; +using System.Xml; +using System.Xml.Serialization; + +namespace WPinternals +{ + internal class PatchEngine + { + internal List PatchDefinitions = new(); + internal readonly List TargetRedirections = new(); + + internal PatchEngine() { } + + internal PatchEngine(string PatchDefinitionsXmlString) + { + XmlSerializer x = new(PatchDefinitions.GetType(), null, Array.Empty(), new XmlRootAttribute("PatchDefinitions"), ""); + MemoryStream s = new(System.Text.Encoding.ASCII.GetBytes(PatchDefinitionsXmlString)); + PatchDefinitions = (List)x.Deserialize(s); + } + + internal void WriteDefinitions(string FilePath) + { + XmlSerializer x = new(PatchDefinitions.GetType(), null, Array.Empty(), new XmlRootAttribute("PatchDefinitions"), ""); + + XmlSerializerNamespaces ns = new(); + ns.Add("", ""); + + StreamWriter FileWriter = new(FilePath); + XmlWriter XmlWriter = XmlWriter.Create(FileWriter, new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true, NewLineHandling = NewLineHandling.Entitize }); + + FileWriter.WriteLine(""); + FileWriter.WriteLine(""); + FileWriter.WriteLine(""); + FileWriter.WriteLine(""); + + x.Serialize(XmlWriter, PatchDefinitions, ns); + + FileWriter.Close(); + } + + private string _TargetPath = null; + internal string TargetPath + { + get + { + return _TargetPath; + } + set + { + _TargetPath = value.TrimEnd(new char[] { '\\' }); + } + } + + private DiscUtils.DiscFileSystem _TargetImage = null; + internal DiscUtils.DiscFileSystem TargetImage + { + get + { + return _TargetImage; + } + set + { + _TargetImage = value; + _TargetPath = ""; + } + } + + internal bool Patch(string PatchDefinition) + { + bool Result = false; + List LoadedFiles = new(); + + LogFile.Log("Attempt patch: " + PatchDefinition); + + // Find a matching TargetVersion + PatchDefinition Definition = PatchDefinitions.Single(d => string.Equals(d.Name, PatchDefinition, StringComparison.CurrentCultureIgnoreCase)); + TargetVersion MatchedVersion = null; + int VersionIndex = 0; + foreach (TargetVersion CurrentVersion in Definition.TargetVersions) + { + bool Match = true; + int FileIndex = 0; + + foreach (TargetFile CurrentTargetFile in CurrentVersion.TargetFiles) + { + // Determine target path + string TargetPath = null; + foreach (TargetRedirection CurrentRedirection in TargetRedirections) + { + if (CurrentTargetFile.Path.StartsWith(CurrentRedirection.RelativePath, StringComparison.OrdinalIgnoreCase)) + { + TargetPath = Path.Combine(CurrentRedirection.TargetPath + "\\", CurrentTargetFile.Path); + break; + } + } + if (TargetPath == null) + { + TargetPath = Path.Combine(this.TargetPath + "\\", CurrentTargetFile.Path); + } + + // Lookup file + FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, TargetPath, StringComparison.CurrentCultureIgnoreCase)); + if (CurrentFile == null) + { + CurrentFile = (TargetImage != null) && (!TargetPath.Contains(':')) + ? new FilePatcher(TargetPath, TargetImage.OpenFile(TargetPath, FileMode.Open, FileAccess.ReadWrite)) + : new FilePatcher(TargetPath); + + LoadedFiles.Add(CurrentFile); + } + + // Compare hash + if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal) && + !StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) + { + Match = false; + + foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) + { + if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) + { + Match = true; // Found match after all. File is patched with an obsolete version of this patch. + break; + } + } + + if (!Match) + { + LogFile.Log("Pattern: " + VersionIndex.ToString() + ", " + FileIndex.ToString()); + break; + } + } + + FileIndex++; + } + + if (Match) + { + MatchedVersion = CurrentVersion; + break; + } + + VersionIndex++; + } + + if (MatchedVersion != null) + { + LogFile.Log("Apply: " + MatchedVersion.Description); + + foreach (TargetFile CurrentTargetFile in MatchedVersion.TargetFiles) + { + FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, Path.Combine(TargetPath + "\\", CurrentTargetFile.Path), StringComparison.CurrentCultureIgnoreCase)); + + if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) + { + CurrentFile.StartPatching(); + + if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal)) + { + // File is already patched, but with an older version of this patch. + // First unpatch back to original. + + foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) + { + if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) + { + foreach (Patch CurrentPatch in CurrentObsoleteFile.Patches) + { + CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes); + } + break; + } + } + } + + foreach (Patch CurrentPatch in CurrentTargetFile.Patches) + { + CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.PatchedBytes); + } + + CurrentFile.FinishPatching(); + } + } + + Result = true; + } + + return Result; + } + + internal void Restore(string PatchDefinition) + { + List LoadedFiles = new(); + + try + { + // Find a matching TargetVersion + PatchDefinition Definition = PatchDefinitions.Single(d => string.Equals(d.Name, PatchDefinition, StringComparison.CurrentCultureIgnoreCase)); + TargetVersion MatchedVersion = null; + foreach (TargetVersion CurrentVersion in Definition.TargetVersions) + { + bool Match = true; + + foreach (TargetFile CurrentTargetFile in CurrentVersion.TargetFiles) + { + // Determine target path + string TargetPath = null; + foreach (TargetRedirection CurrentRedirection in TargetRedirections) + { + if (CurrentTargetFile.Path.StartsWith(CurrentRedirection.RelativePath, StringComparison.OrdinalIgnoreCase)) + { + TargetPath = Path.Combine(CurrentRedirection.TargetPath, CurrentTargetFile.Path); + break; + } + } + if (TargetPath == null) + { + TargetPath = Path.Combine(this.TargetPath, CurrentTargetFile.Path); + } + + // Lookup file + FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, TargetPath, StringComparison.CurrentCultureIgnoreCase)); + if (CurrentFile == null) + { + CurrentFile = new FilePatcher(TargetPath); + LoadedFiles.Add(CurrentFile); + } + + // Compare hash + if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal) && + !StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) + { + Match = false; + + foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) + { + if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) + { + Match = true; // Found match after all. File is patched with an obsolete version of this patch. + break; + } + } + + if (!Match) + { + break; + } + } + } + + if (Match) + { + MatchedVersion = CurrentVersion; + break; + } + } + + if (MatchedVersion != null) + { + foreach (TargetFile CurrentTargetFile in MatchedVersion.TargetFiles) + { + FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Equals(f.FilePath, Path.Combine(TargetPath, CurrentTargetFile.Path), StringComparison.CurrentCultureIgnoreCase)); + + if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal)) + { + CurrentFile.StartPatching(); + + if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched)) + { + foreach (Patch CurrentPatch in CurrentTargetFile.Patches) + { + CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes); + } + } + else + { + foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete) + { + if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched)) + { + foreach (Patch CurrentPatch in CurrentObsoleteFile.Patches) + { + CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes); + } + break; + } + } + } + + CurrentFile.FinishPatching(); + } + } + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + } + } + + internal class TargetRedirection + { + private string _RelativePath; + private string _TargetPath; + + internal TargetRedirection(string RelativePath, string TargetPath) + { + this.RelativePath = RelativePath; + this.TargetPath = TargetPath; + } + + internal string RelativePath + { + get + { + return _RelativePath; + } + set + { + _RelativePath = value.TrimStart(new char[] { '\\' }).TrimEnd(new char[] { '\\' }); + } + } + + internal string TargetPath + { + get + { + return _TargetPath; + } + set + { + _TargetPath = value.TrimEnd(new char[] { '\\' }); + } + } + } + + internal class FilePatcher + { + internal byte[] Hash = null; + internal string FilePath; + internal FileSecurity OriginalACL; + internal Privilege TakeOwnershipPrivilege; + internal Privilege RestorePrivilege; + internal Stream Stream = null; + + internal FilePatcher(string FilePath) + { + this.FilePath = FilePath; + using FileStream stream = File.OpenRead(FilePath); + SHA1Managed sha = new(); + Hash = sha.ComputeHash(stream); + } + + internal FilePatcher(string FilePath, Stream FileStream) + { + if (!FileStream.CanSeek || !FileStream.CanWrite) + { + throw new WPinternalsException("Incorrect filestream", "The provided file stream for patching does not support seeking and/or writing."); + } + + this.FilePath = FilePath; + this.Stream = FileStream; + FileStream.Position = 0; + SHA1Managed sha = new(); + Hash = sha.ComputeHash(FileStream); + FileStream.Position = 0; + } + + internal void StartPatching() + { + if (FilePath.Contains(':')) + { + FileInfo fileInfo = new(FilePath); + + // Enable Take Ownership AND Restore ownership to original owner + // Take Ownership Privilge is not enough. + // We need Restore Privilege. + RestorePrivilege = new Privilege(Privilege.Restore); + RestorePrivilege.Enable(); + + if ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor <= 1)) + { + // On Vista or 7 + TakeOwnershipPrivilege = new Privilege(Privilege.TakeOwnership); + TakeOwnershipPrivilege.Enable(); + } + + // Backup original owner and ACL + OriginalACL = fileInfo.GetAccessControl(); + + // And take the original security to create new security rules. + FileSecurity NewACL = fileInfo.GetAccessControl(); + + // Take ownership + NewACL.SetOwner(WindowsIdentity.GetCurrent().User); + fileInfo.SetAccessControl(NewACL); + + // And create a new access rule + NewACL.SetAccessRule(new FileSystemAccessRule(WindowsIdentity.GetCurrent().User, FileSystemRights.FullControl, AccessControlType.Allow)); + fileInfo.SetAccessControl(NewACL); + + // Open the file for patching + Stream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite); + } + } + + internal void ApplyPatch(UInt32 Offset, byte[] Bytes) + { + Stream.Position = Offset; + Stream.Write(Bytes, 0, Bytes.Length); + } + + internal void FinishPatching() + { + // Close file + Stream.Close(); + + if (FilePath.Contains(':')) + { + FileInfo fileInfo = new(FilePath); + + // Restore original owner and access rules. + // The OriginalACL cannot be reused directly. + FileSecurity NewACL = fileInfo.GetAccessControl(); + NewACL.SetSecurityDescriptorBinaryForm(OriginalACL.GetSecurityDescriptorBinaryForm()); + fileInfo.SetAccessControl(NewACL); + + // Revert to self + RestorePrivilege.Revert(); + RestorePrivilege.Disable(); + + if ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor <= 1)) + { + // On Vista or 7 + TakeOwnershipPrivilege.Revert(); + TakeOwnershipPrivilege.Disable(); + } + } + } + } + + public class PatchDefinition // Must be public to be serializable + { + [XmlAttribute] + public string Name; + + public List TargetVersions = new(); + } + + public class TargetVersion // Must be public to be serializable + { + [XmlAttribute] + public string Description; + + public List TargetFiles = new(); + } + + public class TargetFile // Must be public to be serializable + { + private string _Path; + [XmlAttribute] + public string Path + { + get + { + return _Path; + } + set + { + _Path = value.TrimStart(new char[] { '\\' }); + } + } + + [XmlIgnore] + public byte[] HashOriginal; + [XmlAttribute("HashOriginal")] + public string HashOriginalAsString + { + get + { + return Converter.ConvertHexToString(HashOriginal, ""); + } + set + { + HashOriginal = Converter.ConvertStringToHex(value); + } + } + + [XmlIgnore] + public byte[] HashPatched; + [XmlAttribute("HashPatched")] + public string HashPatchedAsString + { + get + { + return Converter.ConvertHexToString(HashPatched, ""); + } + set + { + HashPatched = Converter.ConvertStringToHex(value); + } + } + + public List Patches = new(); + public List Obsolete = new(); + } + + public class Patch // Must be public to be serializable + { + [XmlIgnore] + public UInt32 Address; + [XmlAttribute("Address")] + public string AddressAsString + { + get + { + return "0x" + Address.ToString("X8"); + } + set + { + string NewValue = value; + if (NewValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + NewValue = NewValue[2..]; + } + + Address = Convert.ToUInt32(NewValue, 16); + } + } + + [XmlIgnore] + public byte[] OriginalBytes; + [XmlAttribute("OriginalBytes")] + public string OriginalBytesAsString + { + get + { + return Converter.ConvertHexToString(OriginalBytes, ""); + } + set + { + OriginalBytes = Converter.ConvertStringToHex(value); + } + } + + [XmlIgnore] + public byte[] PatchedBytes; + [XmlAttribute("PatchedBytes")] + public string PatchedBytesAsString + { + get + { + return Converter.ConvertHexToString(PatchedBytes, ""); + } + set + { + PatchedBytes = Converter.ConvertStringToHex(value); + } + } + } +} diff --git a/Models/Privileges.cs b/WPinternals/Models/Privileges.cs similarity index 97% rename from Models/Privileges.cs rename to WPinternals/Models/Privileges.cs index 441a38a..11f420f 100644 --- a/Models/Privileges.cs +++ b/WPinternals/Models/Privileges.cs @@ -1,637 +1,637 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Specialized; -using System.Runtime.InteropServices; -using System.Threading; - -namespace WPinternals -{ - using Luid = NativeMethods.LUID; - using PrivilegeNotHeldException = System.Security.AccessControl.PrivilegeNotHeldException; - using Win32Exception = System.ComponentModel.Win32Exception; - - internal delegate void PrivilegedCallback(object state); - - internal sealed class Privilege - { - #region Private static members - private static readonly LocalDataStoreSlot tlsSlot = Thread.AllocateDataSlot(); - private static readonly HybridDictionary privileges = new(); - private static readonly HybridDictionary luids = new(); - private static readonly ReaderWriterLock privilegeLock = new(); - #endregion - - #region Private members - private bool needToRevert = false; - private bool initialState = false; - private bool stateWasChanged = false; - private Luid luid; - private readonly Thread currentThread = Thread.CurrentThread; - private TlsContents tlsContents = null; - #endregion - - #region Privilege names - public const string CreateToken = "SeCreateTokenPrivilege"; - public const string AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege"; - public const string LockMemory = "SeLockMemoryPrivilege"; - public const string IncreaseQuota = "SeIncreaseQuotaPrivilege"; - public const string UnsolicitedInput = "SeUnsolicitedInputPrivilege"; - public const string MachineAccount = "SeMachineAccountPrivilege"; - public const string TrustedComputingBase = "SeTcbPrivilege"; - public const string Security = "SeSecurityPrivilege"; - public const string TakeOwnership = "SeTakeOwnershipPrivilege"; - public const string LoadDriver = "SeLoadDriverPrivilege"; - public const string SystemProfile = "SeSystemProfilePrivilege"; - public const string SystemTime = "SeSystemtimePrivilege"; - public const string ProfileSingleProcess = "SeProfileSingleProcessPrivilege"; - public const string IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege"; - public const string CreatePageFile = "SeCreatePagefilePrivilege"; - public const string CreatePermanent = "SeCreatePermanentPrivilege"; - public const string Backup = "SeBackupPrivilege"; - public const string Restore = "SeRestorePrivilege"; - public const string Shutdown = "SeShutdownPrivilege"; - public const string Debug = "SeDebugPrivilege"; - public const string Audit = "SeAuditPrivilege"; - public const string SystemEnvironment = "SeSystemEnvironmentPrivilege"; - public const string ChangeNotify = "SeChangeNotifyPrivilege"; - public const string RemoteShutdown = "SeRemoteShutdownPrivilege"; - public const string Undock = "SeUndockPrivilege"; - public const string SyncAgent = "SeSyncAgentPrivilege"; - public const string EnableDelegation = "SeEnableDelegationPrivilege"; - public const string ManageVolume = "SeManageVolumePrivilege"; - public const string Impersonate = "SeImpersonatePrivilege"; - public const string CreateGlobal = "SeCreateGlobalPrivilege"; - public const string TrustedCredentialManagerAccess = "SeTrustedCredManAccessPrivilege"; - public const string ReserveProcessor = "SeReserveProcessorPrivilege"; - #endregion - - #region LUID caching logic - - // - // This routine is a wrapper around a hashtable containing mappings - // of privilege names to luids - // - - private static Luid LuidFromPrivilege(string privilege) - { - Luid luid; - luid.LowPart = 0; - luid.HighPart = 0; - - // - // Look up the privilege LUID inside the cache - // - - try - { - privilegeLock.AcquireReaderLock(Timeout.Infinite); - - if (luids.Contains(privilege)) - { - luid = (Luid)luids[privilege]; - - privilegeLock.ReleaseReaderLock(); - } - else - { - privilegeLock.ReleaseReaderLock(); - - if (!NativeMethods.LookupPrivilegeValue(null, privilege, ref luid)) - { - int error = Marshal.GetLastWin32Error(); - - if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) - { - throw new OutOfMemoryException(); - } - else if (error == NativeMethods.ERROR_ACCESS_DENIED) - { - throw new UnauthorizedAccessException("Caller does not have the rights to look up privilege local unique identifier"); - } - else if (error == NativeMethods.ERROR_NO_SUCH_PRIVILEGE) - { - throw new ArgumentException( - string.Format("{0} is not a valid privilege name", privilege), - nameof(privilege)); - } - else - { - throw new Win32Exception(error); - } - } - - privilegeLock.AcquireWriterLock(Timeout.Infinite); - } - } - finally - { - if (privilegeLock.IsReaderLockHeld) - { - privilegeLock.ReleaseReaderLock(); - } - - if (privilegeLock.IsWriterLockHeld) - { - if (!luids.Contains(privilege)) - { - luids[privilege] = luid; - privileges[luid] = privilege; - } - - privilegeLock.ReleaseWriterLock(); - } - } - - return luid; - } - #endregion - - #region Nested classes - private sealed class TlsContents : IDisposable - { - private bool disposed = false; - private int referenceCount = 1; - private SafeTokenHandle threadHandle = new(IntPtr.Zero); - - private static SafeTokenHandle processHandle = new(IntPtr.Zero); - private static readonly object syncRoot = new(); - - #region Constructor and finalizer - public TlsContents() - { - int error = 0; - int cachingError = 0; - bool success = true; - - if (processHandle.IsInvalid) - { - lock (syncRoot) - { - if (processHandle.IsInvalid && !NativeMethods.OpenProcessToken( - NativeMethods.GetCurrentProcess(), - TokenAccessLevels.Duplicate, - ref processHandle)) - { - cachingError = Marshal.GetLastWin32Error(); - success = false; - } - } - } - - try - { - // Open the thread token; if there is no thread token, - // copy the process token onto the thread - - if (!NativeMethods.OpenThreadToken( - NativeMethods.GetCurrentThread(), - TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, - true, - ref this.threadHandle)) - { - if (success) - { - error = Marshal.GetLastWin32Error(); - - if (error != NativeMethods.ERROR_NO_TOKEN) - { - success = false; - } - - if (success) - { - error = 0; - - if (!NativeMethods.DuplicateTokenEx( - processHandle, - TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, - IntPtr.Zero, - SecurityImpersonationLevel.Impersonation, - TokenType.Impersonation, - ref this.threadHandle)) - { - error = Marshal.GetLastWin32Error(); - success = false; - } - } - - if (success && !NativeMethods.SetThreadToken( - IntPtr.Zero, - this.threadHandle)) - { - error = Marshal.GetLastWin32Error(); - success = false; - } - - if (success) - { - // This thread is now impersonating; it needs to be reverted to its original state - - this.IsImpersonating = true; - } - } - else - { - error = cachingError; - } - } - else - { - success = true; - } - } - finally - { - if (!success) - { - Dispose(); - } - } - - if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) - { - throw new OutOfMemoryException(); - } - else if (error == NativeMethods.ERROR_ACCESS_DENIED || - error == NativeMethods.ERROR_CANT_OPEN_ANONYMOUS) - { - throw new UnauthorizedAccessException("The caller does not have the rights to perform the operation"); - } - else if (error != 0) - { - throw new Win32Exception(error); - } - } - - ~TlsContents() - { - if (!this.disposed) - { - Dispose(false); - } - } - #endregion - - #region IDisposable implementation - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (this.disposed) - { - return; - } - - if (this.threadHandle != null) - { - this.threadHandle.Dispose(); - this.threadHandle = null; - } - - if (this.IsImpersonating) - { - NativeMethods.RevertToSelf(); - } - - this.disposed = true; - } - #endregion - - #region Reference-counting - public void IncrementReferenceCount() - { - this.referenceCount++; - } - - public int DecrementReferenceCount() - { - int result = --this.referenceCount; - - if (result == 0) - { - Dispose(); - } - - return result; - } - - public int ReferenceCountValue - { - get { return this.referenceCount; } - } - #endregion - - #region Properties - public SafeTokenHandle ThreadHandle - { - get { return this.threadHandle; } - } - - public bool IsImpersonating { get; } = false; - #endregion - } - #endregion - - #region Constructor - public Privilege(string privilegeName) - { - if (privilegeName == null) - { - throw new ArgumentNullException(nameof(privilegeName)); - } - - this.luid = LuidFromPrivilege(privilegeName); - } - #endregion - - #region Public methods and properties - public void Enable() - { - this.ToggleState(true); - } - - public void Disable() - { - this.ToggleState(false); - } - - public void Revert() - { - int error = 0; - - // All privilege operations must take place on the same thread - - if (!this.currentThread.Equals(Thread.CurrentThread)) - { - throw new InvalidOperationException("Operation must take place on the thread that created the object"); - } - - if (!this.NeedToRevert) - { - return; - } - - // This code must be eagerly prepared and non-interruptible. - - try - { - // The payload is entirely in the finally block - // This is how we ensure that the code will not be - // interrupted by catastrophic exceptions - } - finally - { - bool success = true; - - try - { - // Only call AdjustTokenPrivileges if we're not going to be reverting to self, - // on this Revert, since doing the latter obliterates the thread token anyway - - if (this.stateWasChanged && - (this.tlsContents.ReferenceCountValue > 1 || - !this.tlsContents.IsImpersonating)) - { - NativeMethods.TOKEN_PRIVILEGE newState = new(); - newState.PrivilegeCount = 1; - newState.Privilege.Luid = this.luid; - newState.Privilege.Attributes = this.initialState ? NativeMethods.SE_PRIVILEGE_ENABLED : NativeMethods.SE_PRIVILEGE_DISABLED; - - NativeMethods.TOKEN_PRIVILEGE previousState = new(); - uint previousSize = 0; - - if (!NativeMethods.AdjustTokenPrivileges( - this.tlsContents.ThreadHandle, - false, - ref newState, - (uint)Marshal.SizeOf(previousState), - ref previousState, - ref previousSize)) - { - error = Marshal.GetLastWin32Error(); - success = false; - } - } - } - finally - { - if (success) - { - this.Reset(); - } - } - } - - if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) - { - throw new OutOfMemoryException(); - } - else if (error == NativeMethods.ERROR_ACCESS_DENIED) - { - throw new UnauthorizedAccessException("Caller does not have the permission to change the privilege"); - } - else if (error != 0) - { - throw new Win32Exception(error); - } - } - - public bool NeedToRevert - { - get { return this.needToRevert; } - } - - public static void RunWithPrivilege(string privilege, bool enabled, PrivilegedCallback callback, object state) - { - if (callback == null) - { - throw new ArgumentNullException(nameof(callback)); - } - - Privilege p = new(privilege); - - try - { - if (enabled) - { - p.Enable(); - } - else - { - p.Disable(); - } - - callback(state); - } - catch - { - p.Revert(); - throw; - } - finally - { - p.Revert(); - } - } - #endregion - - #region Private implementation - private void ToggleState(bool enable) - { - int error = 0; - - // All privilege operations must take place on the same thread - - if (!this.currentThread.Equals(Thread.CurrentThread)) - { - throw new InvalidOperationException("Operation must take place on the thread that created the object"); - } - - // This privilege was already altered and needs to be reverted before it can be altered again - - if (this.NeedToRevert) - { - throw new InvalidOperationException("Must revert the privilege prior to attempting this operation"); - } - - // Need to make this block of code non-interruptible so that it would preserve - // consistency of thread oken state even in the face of catastrophic exceptions - - try - { - // The payload is entirely in the finally block - // This is how we ensure that the code will not be - // interrupted by catastrophic exceptions - } - finally - { - try - { - // Retrieve TLS state - - this.tlsContents = Thread.GetData(tlsSlot) as TlsContents; - - if (this.tlsContents == null) - { - this.tlsContents = new TlsContents(); - Thread.SetData(tlsSlot, this.tlsContents); - } - else - { - this.tlsContents.IncrementReferenceCount(); - } - - NativeMethods.TOKEN_PRIVILEGE newState = new(); - newState.PrivilegeCount = 1; - newState.Privilege.Luid = this.luid; - newState.Privilege.Attributes = enable ? NativeMethods.SE_PRIVILEGE_ENABLED : NativeMethods.SE_PRIVILEGE_DISABLED; - - NativeMethods.TOKEN_PRIVILEGE previousState = new(); - uint previousSize = 0; - - // Place the new privilege on the thread token and remember the previous state. - - if (!NativeMethods.AdjustTokenPrivileges( - this.tlsContents.ThreadHandle, - false, - ref newState, - (uint)Marshal.SizeOf(previousState), - ref previousState, - ref previousSize)) - { - error = Marshal.GetLastWin32Error(); - } - else if (NativeMethods.ERROR_NOT_ALL_ASSIGNED == Marshal.GetLastWin32Error()) - { - error = NativeMethods.ERROR_NOT_ALL_ASSIGNED; - } - else - { - // This is the initial state that revert will have to go back to - - this.initialState = (previousState.Privilege.Attributes & NativeMethods.SE_PRIVILEGE_ENABLED) != 0; - - // Remember whether state has changed at all - - this.stateWasChanged = this.initialState != enable; - - // If we had to impersonate, or if the privilege state changed we'll need to revert - - this.needToRevert = this.tlsContents.IsImpersonating || this.stateWasChanged; - } - } - finally - { - if (!this.needToRevert) - { - this.Reset(); - } - } - } - - if (error == NativeMethods.ERROR_NOT_ALL_ASSIGNED) - { - throw new PrivilegeNotHeldException(privileges[this.luid] as string); - } - if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) - { - throw new OutOfMemoryException(); - } - else if (error == NativeMethods.ERROR_ACCESS_DENIED || - error == NativeMethods.ERROR_CANT_OPEN_ANONYMOUS) - { - throw new UnauthorizedAccessException("The caller does not have the right to change the privilege"); - } - else if (error != 0) - { - throw new Win32Exception(error); - } - } - - private void Reset() - { - try - { - // Payload is in the finally block - // as a way to guarantee execution - } - finally - { - this.stateWasChanged = false; - this.initialState = false; - this.needToRevert = false; - - if (this.tlsContents?.DecrementReferenceCount() == 0) - { - this.tlsContents = null; - Thread.SetData(tlsSlot, null); - } - } - } - #endregion - } -} - +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Specialized; +using System.Runtime.InteropServices; +using System.Threading; + +namespace WPinternals +{ + using Luid = NativeMethods.LUID; + using PrivilegeNotHeldException = System.Security.AccessControl.PrivilegeNotHeldException; + using Win32Exception = System.ComponentModel.Win32Exception; + + internal delegate void PrivilegedCallback(object state); + + internal sealed class Privilege + { + #region Private static members + private static readonly LocalDataStoreSlot tlsSlot = Thread.AllocateDataSlot(); + private static readonly HybridDictionary privileges = new(); + private static readonly HybridDictionary luids = new(); + private static readonly ReaderWriterLock privilegeLock = new(); + #endregion + + #region Private members + private bool needToRevert = false; + private bool initialState = false; + private bool stateWasChanged = false; + private Luid luid; + private readonly Thread currentThread = Thread.CurrentThread; + private TlsContents tlsContents = null; + #endregion + + #region Privilege names + public const string CreateToken = "SeCreateTokenPrivilege"; + public const string AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege"; + public const string LockMemory = "SeLockMemoryPrivilege"; + public const string IncreaseQuota = "SeIncreaseQuotaPrivilege"; + public const string UnsolicitedInput = "SeUnsolicitedInputPrivilege"; + public const string MachineAccount = "SeMachineAccountPrivilege"; + public const string TrustedComputingBase = "SeTcbPrivilege"; + public const string Security = "SeSecurityPrivilege"; + public const string TakeOwnership = "SeTakeOwnershipPrivilege"; + public const string LoadDriver = "SeLoadDriverPrivilege"; + public const string SystemProfile = "SeSystemProfilePrivilege"; + public const string SystemTime = "SeSystemtimePrivilege"; + public const string ProfileSingleProcess = "SeProfileSingleProcessPrivilege"; + public const string IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege"; + public const string CreatePageFile = "SeCreatePagefilePrivilege"; + public const string CreatePermanent = "SeCreatePermanentPrivilege"; + public const string Backup = "SeBackupPrivilege"; + public const string Restore = "SeRestorePrivilege"; + public const string Shutdown = "SeShutdownPrivilege"; + public const string Debug = "SeDebugPrivilege"; + public const string Audit = "SeAuditPrivilege"; + public const string SystemEnvironment = "SeSystemEnvironmentPrivilege"; + public const string ChangeNotify = "SeChangeNotifyPrivilege"; + public const string RemoteShutdown = "SeRemoteShutdownPrivilege"; + public const string Undock = "SeUndockPrivilege"; + public const string SyncAgent = "SeSyncAgentPrivilege"; + public const string EnableDelegation = "SeEnableDelegationPrivilege"; + public const string ManageVolume = "SeManageVolumePrivilege"; + public const string Impersonate = "SeImpersonatePrivilege"; + public const string CreateGlobal = "SeCreateGlobalPrivilege"; + public const string TrustedCredentialManagerAccess = "SeTrustedCredManAccessPrivilege"; + public const string ReserveProcessor = "SeReserveProcessorPrivilege"; + #endregion + + #region LUID caching logic + + // + // This routine is a wrapper around a hashtable containing mappings + // of privilege names to luids + // + + private static Luid LuidFromPrivilege(string privilege) + { + Luid luid; + luid.LowPart = 0; + luid.HighPart = 0; + + // + // Look up the privilege LUID inside the cache + // + + try + { + privilegeLock.AcquireReaderLock(Timeout.Infinite); + + if (luids.Contains(privilege)) + { + luid = (Luid)luids[privilege]; + + privilegeLock.ReleaseReaderLock(); + } + else + { + privilegeLock.ReleaseReaderLock(); + + if (!NativeMethods.LookupPrivilegeValue(null, privilege, ref luid)) + { + int error = Marshal.GetLastWin32Error(); + + if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) + { + throw new OutOfMemoryException(); + } + else if (error == NativeMethods.ERROR_ACCESS_DENIED) + { + throw new UnauthorizedAccessException("Caller does not have the rights to look up privilege local unique identifier"); + } + else if (error == NativeMethods.ERROR_NO_SUCH_PRIVILEGE) + { + throw new ArgumentException( + string.Format("{0} is not a valid privilege name", privilege), + nameof(privilege)); + } + else + { + throw new Win32Exception(error); + } + } + + privilegeLock.AcquireWriterLock(Timeout.Infinite); + } + } + finally + { + if (privilegeLock.IsReaderLockHeld) + { + privilegeLock.ReleaseReaderLock(); + } + + if (privilegeLock.IsWriterLockHeld) + { + if (!luids.Contains(privilege)) + { + luids[privilege] = luid; + privileges[luid] = privilege; + } + + privilegeLock.ReleaseWriterLock(); + } + } + + return luid; + } + #endregion + + #region Nested classes + private sealed class TlsContents : IDisposable + { + private bool disposed = false; + private int referenceCount = 1; + private SafeTokenHandle threadHandle = new(IntPtr.Zero); + + private static SafeTokenHandle processHandle = new(IntPtr.Zero); + private static readonly object syncRoot = new(); + + #region Constructor and finalizer + public TlsContents() + { + int error = 0; + int cachingError = 0; + bool success = true; + + if (processHandle.IsInvalid) + { + lock (syncRoot) + { + if (processHandle.IsInvalid && !NativeMethods.OpenProcessToken( + NativeMethods.GetCurrentProcess(), + TokenAccessLevels.Duplicate, + ref processHandle)) + { + cachingError = Marshal.GetLastWin32Error(); + success = false; + } + } + } + + try + { + // Open the thread token; if there is no thread token, + // copy the process token onto the thread + + if (!NativeMethods.OpenThreadToken( + NativeMethods.GetCurrentThread(), + TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, + true, + ref this.threadHandle)) + { + if (success) + { + error = Marshal.GetLastWin32Error(); + + if (error != NativeMethods.ERROR_NO_TOKEN) + { + success = false; + } + + if (success) + { + error = 0; + + if (!NativeMethods.DuplicateTokenEx( + processHandle, + TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, + IntPtr.Zero, + SecurityImpersonationLevel.Impersonation, + TokenType.Impersonation, + ref this.threadHandle)) + { + error = Marshal.GetLastWin32Error(); + success = false; + } + } + + if (success && !NativeMethods.SetThreadToken( + IntPtr.Zero, + this.threadHandle)) + { + error = Marshal.GetLastWin32Error(); + success = false; + } + + if (success) + { + // This thread is now impersonating; it needs to be reverted to its original state + + this.IsImpersonating = true; + } + } + else + { + error = cachingError; + } + } + else + { + success = true; + } + } + finally + { + if (!success) + { + Dispose(); + } + } + + if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) + { + throw new OutOfMemoryException(); + } + else if (error == NativeMethods.ERROR_ACCESS_DENIED || + error == NativeMethods.ERROR_CANT_OPEN_ANONYMOUS) + { + throw new UnauthorizedAccessException("The caller does not have the rights to perform the operation"); + } + else if (error != 0) + { + throw new Win32Exception(error); + } + } + + ~TlsContents() + { + if (!this.disposed) + { + Dispose(false); + } + } + #endregion + + #region IDisposable implementation + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (this.disposed) + { + return; + } + + if (this.threadHandle != null) + { + this.threadHandle.Dispose(); + this.threadHandle = null; + } + + if (this.IsImpersonating) + { + NativeMethods.RevertToSelf(); + } + + this.disposed = true; + } + #endregion + + #region Reference-counting + public void IncrementReferenceCount() + { + this.referenceCount++; + } + + public int DecrementReferenceCount() + { + int result = --this.referenceCount; + + if (result == 0) + { + Dispose(); + } + + return result; + } + + public int ReferenceCountValue + { + get { return this.referenceCount; } + } + #endregion + + #region Properties + public SafeTokenHandle ThreadHandle + { + get { return this.threadHandle; } + } + + public bool IsImpersonating { get; } = false; + #endregion + } + #endregion + + #region Constructor + public Privilege(string privilegeName) + { + if (privilegeName == null) + { + throw new ArgumentNullException(nameof(privilegeName)); + } + + this.luid = LuidFromPrivilege(privilegeName); + } + #endregion + + #region Public methods and properties + public void Enable() + { + this.ToggleState(true); + } + + public void Disable() + { + this.ToggleState(false); + } + + public void Revert() + { + int error = 0; + + // All privilege operations must take place on the same thread + + if (!this.currentThread.Equals(Thread.CurrentThread)) + { + throw new InvalidOperationException("Operation must take place on the thread that created the object"); + } + + if (!this.NeedToRevert) + { + return; + } + + // This code must be eagerly prepared and non-interruptible. + + try + { + // The payload is entirely in the finally block + // This is how we ensure that the code will not be + // interrupted by catastrophic exceptions + } + finally + { + bool success = true; + + try + { + // Only call AdjustTokenPrivileges if we're not going to be reverting to self, + // on this Revert, since doing the latter obliterates the thread token anyway + + if (this.stateWasChanged && + (this.tlsContents.ReferenceCountValue > 1 || + !this.tlsContents.IsImpersonating)) + { + NativeMethods.TOKEN_PRIVILEGE newState = new(); + newState.PrivilegeCount = 1; + newState.Privilege.Luid = this.luid; + newState.Privilege.Attributes = this.initialState ? NativeMethods.SE_PRIVILEGE_ENABLED : NativeMethods.SE_PRIVILEGE_DISABLED; + + NativeMethods.TOKEN_PRIVILEGE previousState = new(); + uint previousSize = 0; + + if (!NativeMethods.AdjustTokenPrivileges( + this.tlsContents.ThreadHandle, + false, + ref newState, + (uint)Marshal.SizeOf(previousState), + ref previousState, + ref previousSize)) + { + error = Marshal.GetLastWin32Error(); + success = false; + } + } + } + finally + { + if (success) + { + this.Reset(); + } + } + } + + if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) + { + throw new OutOfMemoryException(); + } + else if (error == NativeMethods.ERROR_ACCESS_DENIED) + { + throw new UnauthorizedAccessException("Caller does not have the permission to change the privilege"); + } + else if (error != 0) + { + throw new Win32Exception(error); + } + } + + public bool NeedToRevert + { + get { return this.needToRevert; } + } + + public static void RunWithPrivilege(string privilege, bool enabled, PrivilegedCallback callback, object state) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + Privilege p = new(privilege); + + try + { + if (enabled) + { + p.Enable(); + } + else + { + p.Disable(); + } + + callback(state); + } + catch + { + p.Revert(); + throw; + } + finally + { + p.Revert(); + } + } + #endregion + + #region Private implementation + private void ToggleState(bool enable) + { + int error = 0; + + // All privilege operations must take place on the same thread + + if (!this.currentThread.Equals(Thread.CurrentThread)) + { + throw new InvalidOperationException("Operation must take place on the thread that created the object"); + } + + // This privilege was already altered and needs to be reverted before it can be altered again + + if (this.NeedToRevert) + { + throw new InvalidOperationException("Must revert the privilege prior to attempting this operation"); + } + + // Need to make this block of code non-interruptible so that it would preserve + // consistency of thread oken state even in the face of catastrophic exceptions + + try + { + // The payload is entirely in the finally block + // This is how we ensure that the code will not be + // interrupted by catastrophic exceptions + } + finally + { + try + { + // Retrieve TLS state + + this.tlsContents = Thread.GetData(tlsSlot) as TlsContents; + + if (this.tlsContents == null) + { + this.tlsContents = new TlsContents(); + Thread.SetData(tlsSlot, this.tlsContents); + } + else + { + this.tlsContents.IncrementReferenceCount(); + } + + NativeMethods.TOKEN_PRIVILEGE newState = new(); + newState.PrivilegeCount = 1; + newState.Privilege.Luid = this.luid; + newState.Privilege.Attributes = enable ? NativeMethods.SE_PRIVILEGE_ENABLED : NativeMethods.SE_PRIVILEGE_DISABLED; + + NativeMethods.TOKEN_PRIVILEGE previousState = new(); + uint previousSize = 0; + + // Place the new privilege on the thread token and remember the previous state. + + if (!NativeMethods.AdjustTokenPrivileges( + this.tlsContents.ThreadHandle, + false, + ref newState, + (uint)Marshal.SizeOf(previousState), + ref previousState, + ref previousSize)) + { + error = Marshal.GetLastWin32Error(); + } + else if (NativeMethods.ERROR_NOT_ALL_ASSIGNED == Marshal.GetLastWin32Error()) + { + error = NativeMethods.ERROR_NOT_ALL_ASSIGNED; + } + else + { + // This is the initial state that revert will have to go back to + + this.initialState = (previousState.Privilege.Attributes & NativeMethods.SE_PRIVILEGE_ENABLED) != 0; + + // Remember whether state has changed at all + + this.stateWasChanged = this.initialState != enable; + + // If we had to impersonate, or if the privilege state changed we'll need to revert + + this.needToRevert = this.tlsContents.IsImpersonating || this.stateWasChanged; + } + } + finally + { + if (!this.needToRevert) + { + this.Reset(); + } + } + } + + if (error == NativeMethods.ERROR_NOT_ALL_ASSIGNED) + { + throw new PrivilegeNotHeldException(privileges[this.luid] as string); + } + if (error == NativeMethods.ERROR_NOT_ENOUGH_MEMORY) + { + throw new OutOfMemoryException(); + } + else if (error == NativeMethods.ERROR_ACCESS_DENIED || + error == NativeMethods.ERROR_CANT_OPEN_ANONYMOUS) + { + throw new UnauthorizedAccessException("The caller does not have the right to change the privilege"); + } + else if (error != 0) + { + throw new Win32Exception(error); + } + } + + private void Reset() + { + try + { + // Payload is in the finally block + // as a way to guarantee execution + } + finally + { + this.stateWasChanged = false; + this.initialState = false; + this.needToRevert = false; + + if (this.tlsContents?.DecrementReferenceCount() == 0) + { + this.tlsContents = null; + Thread.SetData(tlsSlot, null); + } + } + } + #endregion + } +} + diff --git a/Models/QualcommDownload.cs b/WPinternals/Models/QualcommDownload.cs similarity index 97% rename from Models/QualcommDownload.cs rename to WPinternals/Models/QualcommDownload.cs index 5cca117..078a06c 100644 --- a/Models/QualcommDownload.cs +++ b/WPinternals/Models/QualcommDownload.cs @@ -1,145 +1,145 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Linq; - -namespace WPinternals -{ - internal class QualcommDownload - { - private readonly QualcommSerial Serial; - - public QualcommDownload(QualcommSerial Serial) - { - this.Serial = Serial; - } - - public bool IsAlive() - { - try - { - Serial.SendCommand(new byte[] { 0x06 }, new byte[] { 0x02 }); - return true; - } - catch - { - return false; - } - } - - public void SendToPhoneMemory(UInt32 Address, Stream Data, UInt32 Length = UInt32.MaxValue) - { - long Remaining = Length > (Data.Length - Data.Position) ? Data.Length - Data.Position : Length; - UInt32 CurrentLength; - byte[] Buffer = new byte[0x107]; - Buffer[0] = 0x0F; - System.Buffer.BlockCopy(BitConverter.GetBytes((UInt16)0x100).Reverse().ToArray(), 0, Buffer, 5, 2); // Length is in Big Endian - UInt32 CurrentAddress = Address; - while (Remaining > 0) - { - System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentAddress).Reverse().ToArray(), 0, Buffer, 1, 4); // Address is in Big Endian - - CurrentLength = Remaining >= 0x100 ? 0x100 : (UInt32)Remaining; - - CurrentLength = (UInt32)Data.Read(Buffer, 7, (int)CurrentLength); - Serial.SendCommand(Buffer, new byte[] { 0x02 }); - - CurrentAddress += CurrentLength; - Remaining -= CurrentLength; - } - } - - public void SendToPhoneMemory(UInt32 Address, byte[] Data, UInt32 Offset = 0, UInt32 Length = UInt32.MaxValue) - { - long Remaining; - if (Offset > (Data.Length - 1)) - { - throw new ArgumentException("Wrong offset"); - } - - Remaining = Length > (Data.Length - Offset) ? Data.Length - Offset : Length; - - UInt32 CurrentLength; - UInt32 CurrentOffset = Offset; - byte[] Buffer = new byte[0x107]; - UInt32 CurrentAddress = Address; - byte[] CurrentBytes; - while (Remaining > 0) - { - if (Remaining >= 0x100) - { - CurrentLength = 0x100; - CurrentBytes = Buffer; - } - else - { - CurrentLength = (UInt32)Remaining; - CurrentBytes = new byte[CurrentLength + 7]; - } - CurrentBytes[0] = 0x0F; - System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentAddress).Reverse().ToArray(), 0, CurrentBytes, 1, 4); // Address is in Big Endian - System.Buffer.BlockCopy(BitConverter.GetBytes((UInt16)CurrentLength).Reverse().ToArray(), 0, CurrentBytes, 5, 2); // Length is in Big Endian - System.Buffer.BlockCopy(Data, (int)CurrentOffset, CurrentBytes, 7, (int)CurrentLength); - - Serial.SendCommand(CurrentBytes, new byte[] { 0x02 }); - - CurrentAddress += CurrentLength; - CurrentOffset += CurrentLength; - Remaining -= CurrentLength; - } - } - - public void StartBootloader(UInt32 Address) - { - byte[] Buffer = new byte[5]; - Buffer[0] = 0x05; - System.Buffer.BlockCopy(BitConverter.GetBytes(Address).Reverse().ToArray(), 0, Buffer, 1, 4); // Address is in Big Endian - Serial.SendCommand(Buffer, new byte[] { 0x02 }); - } - - // Reset interface. Interface becomes unresponsive. - public void Reset() - { - Serial.SendCommand(new byte[] { 0x0A }, new byte[] { 0x02 }); - } - - // This also resets interface. This does not actually reboot the phone. The interface becomes unresponsive. - public void Shutdown() - { - Serial.SendCommand(new byte[] { 0x0E }, new byte[] { 0x02 }); - } - - // This command only works on 9008 interface. - public byte[] GetRKH() - { - byte[] Response = Serial.SendCommand(new byte[] { 0x18 }, new byte[] { 0x18, 0x01, 0x00 }); - byte[] Result = new byte[0x20]; - Buffer.BlockCopy(Response, 3, Result, 0, 0x20); - return Result; - } - - public void CloseSerial() - { - Serial.Close(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; + +namespace WPinternals +{ + internal class QualcommDownload + { + private readonly QualcommSerial Serial; + + public QualcommDownload(QualcommSerial Serial) + { + this.Serial = Serial; + } + + public bool IsAlive() + { + try + { + Serial.SendCommand(new byte[] { 0x06 }, new byte[] { 0x02 }); + return true; + } + catch + { + return false; + } + } + + public void SendToPhoneMemory(UInt32 Address, Stream Data, UInt32 Length = UInt32.MaxValue) + { + long Remaining = Length > (Data.Length - Data.Position) ? Data.Length - Data.Position : Length; + UInt32 CurrentLength; + byte[] Buffer = new byte[0x107]; + Buffer[0] = 0x0F; + System.Buffer.BlockCopy(BitConverter.GetBytes((UInt16)0x100).Reverse().ToArray(), 0, Buffer, 5, 2); // Length is in Big Endian + UInt32 CurrentAddress = Address; + while (Remaining > 0) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentAddress).Reverse().ToArray(), 0, Buffer, 1, 4); // Address is in Big Endian + + CurrentLength = Remaining >= 0x100 ? 0x100 : (UInt32)Remaining; + + CurrentLength = (UInt32)Data.Read(Buffer, 7, (int)CurrentLength); + Serial.SendCommand(Buffer, new byte[] { 0x02 }); + + CurrentAddress += CurrentLength; + Remaining -= CurrentLength; + } + } + + public void SendToPhoneMemory(UInt32 Address, byte[] Data, UInt32 Offset = 0, UInt32 Length = UInt32.MaxValue) + { + long Remaining; + if (Offset > (Data.Length - 1)) + { + throw new ArgumentException("Wrong offset"); + } + + Remaining = Length > (Data.Length - Offset) ? Data.Length - Offset : Length; + + UInt32 CurrentLength; + UInt32 CurrentOffset = Offset; + byte[] Buffer = new byte[0x107]; + UInt32 CurrentAddress = Address; + byte[] CurrentBytes; + while (Remaining > 0) + { + if (Remaining >= 0x100) + { + CurrentLength = 0x100; + CurrentBytes = Buffer; + } + else + { + CurrentLength = (UInt32)Remaining; + CurrentBytes = new byte[CurrentLength + 7]; + } + CurrentBytes[0] = 0x0F; + System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentAddress).Reverse().ToArray(), 0, CurrentBytes, 1, 4); // Address is in Big Endian + System.Buffer.BlockCopy(BitConverter.GetBytes((UInt16)CurrentLength).Reverse().ToArray(), 0, CurrentBytes, 5, 2); // Length is in Big Endian + System.Buffer.BlockCopy(Data, (int)CurrentOffset, CurrentBytes, 7, (int)CurrentLength); + + Serial.SendCommand(CurrentBytes, new byte[] { 0x02 }); + + CurrentAddress += CurrentLength; + CurrentOffset += CurrentLength; + Remaining -= CurrentLength; + } + } + + public void StartBootloader(UInt32 Address) + { + byte[] Buffer = new byte[5]; + Buffer[0] = 0x05; + System.Buffer.BlockCopy(BitConverter.GetBytes(Address).Reverse().ToArray(), 0, Buffer, 1, 4); // Address is in Big Endian + Serial.SendCommand(Buffer, new byte[] { 0x02 }); + } + + // Reset interface. Interface becomes unresponsive. + public void Reset() + { + Serial.SendCommand(new byte[] { 0x0A }, new byte[] { 0x02 }); + } + + // This also resets interface. This does not actually reboot the phone. The interface becomes unresponsive. + public void Shutdown() + { + Serial.SendCommand(new byte[] { 0x0E }, new byte[] { 0x02 }); + } + + // This command only works on 9008 interface. + public byte[] GetRKH() + { + byte[] Response = Serial.SendCommand(new byte[] { 0x18 }, new byte[] { 0x18, 0x01, 0x00 }); + byte[] Result = new byte[0x20]; + Buffer.BlockCopy(Response, 3, Result, 0, 0x20); + return Result; + } + + public void CloseSerial() + { + Serial.Close(); + } + } +} diff --git a/Models/QualcommFlasher.cs b/WPinternals/Models/QualcommFlasher.cs similarity index 97% rename from Models/QualcommFlasher.cs rename to WPinternals/Models/QualcommFlasher.cs index 34528f8..b687ec6 100644 --- a/Models/QualcommFlasher.cs +++ b/WPinternals/Models/QualcommFlasher.cs @@ -1,228 +1,228 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; - -namespace WPinternals -{ - internal enum FlashUnit - { - Bytes, - Sectors - } - - internal class QualcommFlasher - { - private readonly QualcommSerial Serial; - - public QualcommFlasher(QualcommSerial Serial) - { - this.Serial = Serial; - } - - public void CloseSerial() - { - Serial.Close(); - } - - public void Hello() - { - byte[] Command = new byte[] - { - 0x01, // Hello command - 0x51, 0x43, 0x4F, 0x4D, 0x20, 0x66, 0x61, 0x73, 0x74, 0x20, 0x64, 0x6F, 0x77, 0x6E, 0x6C, 0x6F, // "QCOM fast download protocol host" - 0x61, 0x64, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x68, 0x6F, 0x73, 0x74, - 0x02, - 0x02, // Protocol version - Must be at least 0x02 - 0x01 - }; - - Serial.SendCommand(Command, new byte[] { 0x02 }); - } - - public void SetSecurityMode(byte Mode) - { - byte[] Command = new byte[2]; - Command[0] = 0x17; - Command[1] = Mode; - - Serial.SendCommand(Command, new byte[] { 0x18 }); - } - - // Use PartitionID 0x21 - public void OpenPartition(byte PartitionID) - { - byte[] Command = new byte[2]; - Command[0] = 0x1B; - Command[1] = PartitionID; - - Serial.SendCommand(Command, new byte[] { 0x1C }); - } - - public void ClosePartition() - { - Serial.SendCommand(new byte[] { 0x15 }, new byte[] { 0x16 }); - } - - public void Flash(UInt32 StartInBytes, Stream Data, UInt32 LengthInBytes = UInt32.MaxValue) - { - Flash(StartInBytes, Data, null, null, LengthInBytes); - } - - public void Flash(UInt32 StartInBytes, Stream Data, Action ProgressUpdateCallback, UInt32 LengthInBytes = UInt32.MaxValue) - { - Flash(StartInBytes, Data, ProgressUpdateCallback, null, LengthInBytes); - } - - public void Flash(UInt32 StartInBytes, Stream Data, ProgressUpdater UpdaterPerSector, UInt32 LengthInBytes = UInt32.MaxValue) - { - Flash(StartInBytes, Data, null, UpdaterPerSector, LengthInBytes); - } - - public void Flash(UInt32 StartInBytes, Stream Data, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector, UInt32 LengthInBytes = UInt32.MaxValue) - { - long Remaining = (LengthInBytes == UInt32.MaxValue) || (LengthInBytes > (Data.Length - Data.Position)) - ? Data.Length - Data.Position - : LengthInBytes; - UInt32 CurrentLength; - byte[] Buffer = new byte[0x405]; - byte[] ResponsePattern = new byte[5]; - byte[] FinalCommand; - Buffer[0] = 0x07; - ResponsePattern[0] = 0x08; - UInt32 CurrentPosition = StartInBytes; - - ProgressUpdater Progress = UpdaterPerSector; - if ((Progress == null) && (ProgressUpdateCallback != null)) - { - Progress = new ProgressUpdater(GetSectorCount((UInt64)Remaining), ProgressUpdateCallback); - } - - while (Remaining > 0) - { - System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, Buffer, 1, 4); // Start is in bytes and in Little Endian (on Samsung devices start is in sectors!) - System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, ResponsePattern, 1, 4); // Start is in bytes and in Little Endian (on Samsung devices start is in sectors!) - - CurrentLength = Remaining >= 0x400 ? 0x400 : (UInt32)Remaining; - - CurrentLength = (uint)Data.Read(Buffer, 5, (int)CurrentLength); - - if (CurrentLength < 0x400) - { - FinalCommand = new byte[CurrentLength + 5]; - System.Buffer.BlockCopy(Buffer, 0, FinalCommand, 0, (int)CurrentLength + 5); - } - else - { - FinalCommand = Buffer; - } - - Serial.SendCommand(FinalCommand, ResponsePattern); - - CurrentPosition += CurrentLength; - Remaining -= CurrentLength; - - Progress?.IncreaseProgress(GetSectorCount(CurrentLength)); - } - } - - public void Flash(UInt32 StartInBytes, byte[] Data, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) - { - Flash(StartInBytes, Data, null, null, OffsetInBytes, LengthInBytes); - } - - public void Flash(UInt32 StartInBytes, byte[] Data, Action ProgressUpdateCallback, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) - { - Flash(StartInBytes, Data, ProgressUpdateCallback, null, OffsetInBytes, LengthInBytes); - } - - public void Flash(UInt32 StartInBytes, byte[] Data, ProgressUpdater UpdaterPerSector, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) - { - Flash(StartInBytes, Data, null, UpdaterPerSector, OffsetInBytes, LengthInBytes); - } - - public void Flash(UInt32 StartInBytes, byte[] Data, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) - { - long RemainingBytes; - if (OffsetInBytes > (Data.Length - 1)) - { - throw new ArgumentException("Wrong offset"); - } - - RemainingBytes = (LengthInBytes == UInt32.MaxValue) || (LengthInBytes > (Data.Length - OffsetInBytes)) - ? Data.Length - OffsetInBytes - : LengthInBytes; - - UInt32 CurrentLength; - UInt32 CurrentOffset = OffsetInBytes; - byte[] Buffer = new byte[0x405]; - byte[] ResponsePattern = new byte[5]; - byte[] FinalCommand; - Buffer[0] = 0x07; - ResponsePattern[0] = 0x08; - UInt32 CurrentPosition = StartInBytes; - - ProgressUpdater Progress = UpdaterPerSector; - if ((Progress == null) && (ProgressUpdateCallback != null)) - { - Progress = new ProgressUpdater(GetSectorCount((UInt64)RemainingBytes), ProgressUpdateCallback); - } - - while (RemainingBytes > 0) - { - System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, Buffer, 1, 4); // Start position is in bytes and in Little Endian (on Samsung phones the start position is in Sectors!!) - System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, ResponsePattern, 1, 4); // Start position is in bytes and in Little Endian (on Samsung phones the start position is in Sectors!!) - - CurrentLength = RemainingBytes >= 0x400 ? 0x400 : (UInt32)RemainingBytes; - - System.Buffer.BlockCopy(Data, (int)CurrentOffset, Buffer, 5, (int)CurrentLength); - - if (CurrentLength < 0x400) - { - FinalCommand = new byte[CurrentLength + 5]; - System.Buffer.BlockCopy(Buffer, 0, FinalCommand, 0, (int)CurrentLength + 5); - } - else - { - FinalCommand = Buffer; - } - - Serial.SendCommand(FinalCommand, ResponsePattern); - - CurrentPosition += CurrentLength; - CurrentOffset += CurrentLength; - RemainingBytes -= CurrentLength; - - Progress?.IncreaseProgress(GetSectorCount(CurrentLength)); - } - } - - public UInt64 GetSectorCount(UInt64 ByteCount) - { - return (ByteCount / 0x200) + ((ByteCount % 0x200) > 0 ? 1 : (UInt64)0); - } - - public void Reboot() - { - Serial.SendCommand(new byte[] { 0x0B }, new byte[] { 0x0C }); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; + +namespace WPinternals +{ + internal enum FlashUnit + { + Bytes, + Sectors + } + + internal class QualcommFlasher + { + private readonly QualcommSerial Serial; + + public QualcommFlasher(QualcommSerial Serial) + { + this.Serial = Serial; + } + + public void CloseSerial() + { + Serial.Close(); + } + + public void Hello() + { + byte[] Command = new byte[] + { + 0x01, // Hello command + 0x51, 0x43, 0x4F, 0x4D, 0x20, 0x66, 0x61, 0x73, 0x74, 0x20, 0x64, 0x6F, 0x77, 0x6E, 0x6C, 0x6F, // "QCOM fast download protocol host" + 0x61, 0x64, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x68, 0x6F, 0x73, 0x74, + 0x02, + 0x02, // Protocol version - Must be at least 0x02 + 0x01 + }; + + Serial.SendCommand(Command, new byte[] { 0x02 }); + } + + public void SetSecurityMode(byte Mode) + { + byte[] Command = new byte[2]; + Command[0] = 0x17; + Command[1] = Mode; + + Serial.SendCommand(Command, new byte[] { 0x18 }); + } + + // Use PartitionID 0x21 + public void OpenPartition(byte PartitionID) + { + byte[] Command = new byte[2]; + Command[0] = 0x1B; + Command[1] = PartitionID; + + Serial.SendCommand(Command, new byte[] { 0x1C }); + } + + public void ClosePartition() + { + Serial.SendCommand(new byte[] { 0x15 }, new byte[] { 0x16 }); + } + + public void Flash(UInt32 StartInBytes, Stream Data, UInt32 LengthInBytes = UInt32.MaxValue) + { + Flash(StartInBytes, Data, null, null, LengthInBytes); + } + + public void Flash(UInt32 StartInBytes, Stream Data, Action ProgressUpdateCallback, UInt32 LengthInBytes = UInt32.MaxValue) + { + Flash(StartInBytes, Data, ProgressUpdateCallback, null, LengthInBytes); + } + + public void Flash(UInt32 StartInBytes, Stream Data, ProgressUpdater UpdaterPerSector, UInt32 LengthInBytes = UInt32.MaxValue) + { + Flash(StartInBytes, Data, null, UpdaterPerSector, LengthInBytes); + } + + public void Flash(UInt32 StartInBytes, Stream Data, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector, UInt32 LengthInBytes = UInt32.MaxValue) + { + long Remaining = (LengthInBytes == UInt32.MaxValue) || (LengthInBytes > (Data.Length - Data.Position)) + ? Data.Length - Data.Position + : LengthInBytes; + UInt32 CurrentLength; + byte[] Buffer = new byte[0x405]; + byte[] ResponsePattern = new byte[5]; + byte[] FinalCommand; + Buffer[0] = 0x07; + ResponsePattern[0] = 0x08; + UInt32 CurrentPosition = StartInBytes; + + ProgressUpdater Progress = UpdaterPerSector; + if ((Progress == null) && (ProgressUpdateCallback != null)) + { + Progress = new ProgressUpdater(GetSectorCount((UInt64)Remaining), ProgressUpdateCallback); + } + + while (Remaining > 0) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, Buffer, 1, 4); // Start is in bytes and in Little Endian (on Samsung devices start is in sectors!) + System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, ResponsePattern, 1, 4); // Start is in bytes and in Little Endian (on Samsung devices start is in sectors!) + + CurrentLength = Remaining >= 0x400 ? 0x400 : (UInt32)Remaining; + + CurrentLength = (uint)Data.Read(Buffer, 5, (int)CurrentLength); + + if (CurrentLength < 0x400) + { + FinalCommand = new byte[CurrentLength + 5]; + System.Buffer.BlockCopy(Buffer, 0, FinalCommand, 0, (int)CurrentLength + 5); + } + else + { + FinalCommand = Buffer; + } + + Serial.SendCommand(FinalCommand, ResponsePattern); + + CurrentPosition += CurrentLength; + Remaining -= CurrentLength; + + Progress?.IncreaseProgress(GetSectorCount(CurrentLength)); + } + } + + public void Flash(UInt32 StartInBytes, byte[] Data, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) + { + Flash(StartInBytes, Data, null, null, OffsetInBytes, LengthInBytes); + } + + public void Flash(UInt32 StartInBytes, byte[] Data, Action ProgressUpdateCallback, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) + { + Flash(StartInBytes, Data, ProgressUpdateCallback, null, OffsetInBytes, LengthInBytes); + } + + public void Flash(UInt32 StartInBytes, byte[] Data, ProgressUpdater UpdaterPerSector, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) + { + Flash(StartInBytes, Data, null, UpdaterPerSector, OffsetInBytes, LengthInBytes); + } + + public void Flash(UInt32 StartInBytes, byte[] Data, Action ProgressUpdateCallback, ProgressUpdater UpdaterPerSector, UInt32 OffsetInBytes = 0, UInt32 LengthInBytes = UInt32.MaxValue) + { + long RemainingBytes; + if (OffsetInBytes > (Data.Length - 1)) + { + throw new ArgumentException("Wrong offset"); + } + + RemainingBytes = (LengthInBytes == UInt32.MaxValue) || (LengthInBytes > (Data.Length - OffsetInBytes)) + ? Data.Length - OffsetInBytes + : LengthInBytes; + + UInt32 CurrentLength; + UInt32 CurrentOffset = OffsetInBytes; + byte[] Buffer = new byte[0x405]; + byte[] ResponsePattern = new byte[5]; + byte[] FinalCommand; + Buffer[0] = 0x07; + ResponsePattern[0] = 0x08; + UInt32 CurrentPosition = StartInBytes; + + ProgressUpdater Progress = UpdaterPerSector; + if ((Progress == null) && (ProgressUpdateCallback != null)) + { + Progress = new ProgressUpdater(GetSectorCount((UInt64)RemainingBytes), ProgressUpdateCallback); + } + + while (RemainingBytes > 0) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, Buffer, 1, 4); // Start position is in bytes and in Little Endian (on Samsung phones the start position is in Sectors!!) + System.Buffer.BlockCopy(BitConverter.GetBytes(CurrentPosition), 0, ResponsePattern, 1, 4); // Start position is in bytes and in Little Endian (on Samsung phones the start position is in Sectors!!) + + CurrentLength = RemainingBytes >= 0x400 ? 0x400 : (UInt32)RemainingBytes; + + System.Buffer.BlockCopy(Data, (int)CurrentOffset, Buffer, 5, (int)CurrentLength); + + if (CurrentLength < 0x400) + { + FinalCommand = new byte[CurrentLength + 5]; + System.Buffer.BlockCopy(Buffer, 0, FinalCommand, 0, (int)CurrentLength + 5); + } + else + { + FinalCommand = Buffer; + } + + Serial.SendCommand(FinalCommand, ResponsePattern); + + CurrentPosition += CurrentLength; + CurrentOffset += CurrentLength; + RemainingBytes -= CurrentLength; + + Progress?.IncreaseProgress(GetSectorCount(CurrentLength)); + } + } + + public UInt64 GetSectorCount(UInt64 ByteCount) + { + return (ByteCount / 0x200) + ((ByteCount % 0x200) > 0 ? 1 : (UInt64)0); + } + + public void Reboot() + { + Serial.SendCommand(new byte[] { 0x0B }, new byte[] { 0x0C }); + } + } +} diff --git a/Models/QualcommLoader.cs b/WPinternals/Models/QualcommLoader.cs similarity index 97% rename from Models/QualcommLoader.cs rename to WPinternals/Models/QualcommLoader.cs index 390abaa..2506092 100644 --- a/Models/QualcommLoader.cs +++ b/WPinternals/Models/QualcommLoader.cs @@ -1,124 +1,124 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; - -namespace WPinternals -{ - internal static class QualcommLoaders - { - internal static List GetPossibleLoadersForRootKeyHash(string Path, byte[] RootKeyHash) - { - List Result = new(); - - try - { - foreach (string FilePath in Directory.EnumerateFiles(Path)) - { - try - { - FileInfo Info = new(FilePath); - if (Info.Length <= 0x80000) - { - QualcommPartition Loader; - -#if DEBUG - System.Diagnostics.Debug.Print("Evaluating loader: " + FilePath); -#endif - - byte[] Binary = ParseAsHexFile(FilePath); - Loader = Binary == null ? new QualcommPartition(FilePath) : new QualcommPartition(Binary); - - // Make sure the RootKeyHash is not blank - // If the RootKeyHash is blank, this is an engineering device, and it will accept any RKH - // We expect the user to know what he is doing in such case and we will ignore checks - if (!StructuralComparisons.StructuralEqualityComparer.Equals(RootKeyHash, new byte[RootKeyHash.Length])) - { - if (StructuralComparisons.StructuralEqualityComparer.Equals(Loader.RootKeyHash, RootKeyHash) - && (ByteOperations.FindUnicode(Loader.Binary, "QHSUSB_ARMPRG") != null)) // To detect that this is a loader, and not SBL1 or something. V1 loaders are QHSUSB_ARMPRG. V2 loaders are QHSUSB__BULK. Only V1 supported for now, because V2 only accepts signed payload. - { - Result.Add(Loader); - } - } - else - { - if (ByteOperations.FindUnicode(Loader.Binary, "QHSUSB_ARMPRG") != null) // To detect that this is a loader, and not SBL1 or something. V1 loaders are QHSUSB_ARMPRG. V2 loaders are QHSUSB__BULK. Only V1 supported for now, because V2 only accepts signed payload. - { - Result.Add(Loader); - } - } - } - } - catch { } - } - } - catch { } - - return Result; - } - - internal static byte[] ParseAsHexFile(string FilePath) - { - byte[] Result = null; - - try - { - string[] Lines = File.ReadAllLines(FilePath); - byte[] Buffer = null; - int BufferSize = 0; - - foreach (string Line in Lines) - { - if (Line[0] != ':') - { - throw new BadImageFormatException(); - } - - byte[] LineBytes = Converter.ConvertStringToHex(Line[1..]); - - if ((LineBytes[0] + 5) != LineBytes.Length) - { - throw new BadImageFormatException(); - } - - if (Buffer == null) - { - Buffer = new byte[0x40000]; - } - - if (LineBytes[3] == 0) // This is mem data - { - System.Buffer.BlockCopy(LineBytes, 4, Buffer, BufferSize, LineBytes[0]); - BufferSize += LineBytes[0]; - } - } - - Result = new byte[BufferSize]; - System.Buffer.BlockCopy(Buffer, 0, Result, 0, BufferSize); - } - catch { } - - return Result; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace WPinternals +{ + internal static class QualcommLoaders + { + internal static List GetPossibleLoadersForRootKeyHash(string Path, byte[] RootKeyHash) + { + List Result = new(); + + try + { + foreach (string FilePath in Directory.EnumerateFiles(Path)) + { + try + { + FileInfo Info = new(FilePath); + if (Info.Length <= 0x80000) + { + QualcommPartition Loader; + +#if DEBUG + System.Diagnostics.Debug.Print("Evaluating loader: " + FilePath); +#endif + + byte[] Binary = ParseAsHexFile(FilePath); + Loader = Binary == null ? new QualcommPartition(FilePath) : new QualcommPartition(Binary); + + // Make sure the RootKeyHash is not blank + // If the RootKeyHash is blank, this is an engineering device, and it will accept any RKH + // We expect the user to know what he is doing in such case and we will ignore checks + if (!StructuralComparisons.StructuralEqualityComparer.Equals(RootKeyHash, new byte[RootKeyHash.Length])) + { + if (StructuralComparisons.StructuralEqualityComparer.Equals(Loader.RootKeyHash, RootKeyHash) + && (ByteOperations.FindUnicode(Loader.Binary, "QHSUSB_ARMPRG") != null)) // To detect that this is a loader, and not SBL1 or something. V1 loaders are QHSUSB_ARMPRG. V2 loaders are QHSUSB__BULK. Only V1 supported for now, because V2 only accepts signed payload. + { + Result.Add(Loader); + } + } + else + { + if (ByteOperations.FindUnicode(Loader.Binary, "QHSUSB_ARMPRG") != null) // To detect that this is a loader, and not SBL1 or something. V1 loaders are QHSUSB_ARMPRG. V2 loaders are QHSUSB__BULK. Only V1 supported for now, because V2 only accepts signed payload. + { + Result.Add(Loader); + } + } + } + } + catch { } + } + } + catch { } + + return Result; + } + + internal static byte[] ParseAsHexFile(string FilePath) + { + byte[] Result = null; + + try + { + string[] Lines = File.ReadAllLines(FilePath); + byte[] Buffer = null; + int BufferSize = 0; + + foreach (string Line in Lines) + { + if (Line[0] != ':') + { + throw new BadImageFormatException(); + } + + byte[] LineBytes = Converter.ConvertStringToHex(Line[1..]); + + if ((LineBytes[0] + 5) != LineBytes.Length) + { + throw new BadImageFormatException(); + } + + if (Buffer == null) + { + Buffer = new byte[0x40000]; + } + + if (LineBytes[3] == 0) // This is mem data + { + System.Buffer.BlockCopy(LineBytes, 4, Buffer, BufferSize, LineBytes[0]); + BufferSize += LineBytes[0]; + } + } + + Result = new byte[BufferSize]; + System.Buffer.BlockCopy(Buffer, 0, Result, 0, BufferSize); + } + catch { } + + return Result; + } + } +} diff --git a/Models/QualcommPartition.cs b/WPinternals/Models/QualcommPartition.cs similarity index 97% rename from Models/QualcommPartition.cs rename to WPinternals/Models/QualcommPartition.cs index ff4ce53..9281ddb 100644 --- a/Models/QualcommPartition.cs +++ b/WPinternals/Models/QualcommPartition.cs @@ -1,174 +1,174 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Security.Cryptography; - -namespace WPinternals -{ - internal enum QualcommPartitionHeaderType - { - Long, - Short - }; - - internal class QualcommPartition - { - internal byte[] Binary; - internal uint HeaderOffset; - internal QualcommPartitionHeaderType HeaderType; - internal uint ImageOffset; - internal uint ImageAddress; - internal uint ImageSize; - internal uint CodeSize; - internal uint SignatureAddress; - internal uint SignatureSize; - internal uint SignatureOffset; - internal uint CertificatesAddress; - internal uint CertificatesSize; - internal uint CertificatesOffset; - internal byte[] RootKeyHash = null; - - internal QualcommPartition(string Path) : this(File.ReadAllBytes(Path)) { } - - internal QualcommPartition(byte[] Binary, uint Offset = 0) - { -#if DEBUG - System.Diagnostics.Debug.Print("Loader: " + Converter.ConvertHexToString(new SHA256Managed().ComputeHash(Binary, 0, Binary.Length), "")); -#endif - - this.Binary = Binary; - - byte[] LongHeaderPattern = new byte[] { 0xD1, 0xDC, 0x4B, 0x84, 0x34, 0x10, 0xD7, 0x73, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - byte[] LongHeaderMask = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - if (ByteOperations.FindPattern(Binary, Offset, 4, new byte[] { 0x7F, 0x45, 0x4C, 0x46 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, null) == 0) - { - // This is an ELF image - // First program header is a reference to the elf-header - // Second program header is a reference to the signed hash-table - HeaderType = QualcommPartitionHeaderType.Short; - UInt32 ProgramHeaderOffset; - UInt16 ProgramHeaderEntrySize; - UInt32 HashTableProgramHeaderOffset; - if (Binary[Offset + 0x04] == 1) - { - // 32-bit elf image - ProgramHeaderOffset = Offset + ByteOperations.ReadUInt32(Binary, Offset + 0x1c); - ProgramHeaderEntrySize = ByteOperations.ReadUInt16(Binary, Offset + 0x2a); - HashTableProgramHeaderOffset = ProgramHeaderOffset + ProgramHeaderEntrySize; - ImageOffset = Offset + ByteOperations.ReadUInt32(Binary, HashTableProgramHeaderOffset + 0x04); - HeaderOffset = ImageOffset + 8; - } - else if (Binary[Offset + 0x04] == 2) - { - // 64-bit elf image - ProgramHeaderOffset = Offset + ByteOperations.ReadUInt32(Binary, Offset + 0x20); - ProgramHeaderEntrySize = ByteOperations.ReadUInt16(Binary, Offset + 0x36); - HashTableProgramHeaderOffset = ProgramHeaderOffset + ProgramHeaderEntrySize; - ImageOffset = Offset + (UInt32)ByteOperations.ReadUInt64(Binary, HashTableProgramHeaderOffset + 0x08); - HeaderOffset = ImageOffset + 8; - } - else - { - throw new WPinternalsException("Invalid programmer", "The type of elf image could not be determined from the provided programmer."); - } - } - else if (ByteOperations.FindPattern(Binary, Offset, (uint)LongHeaderPattern.Length, LongHeaderPattern, LongHeaderMask, null) == null) - { - HeaderType = QualcommPartitionHeaderType.Short; - ImageOffset = Offset; - HeaderOffset = ImageOffset + 8; - } - else - { - HeaderType = QualcommPartitionHeaderType.Long; - ImageOffset = Offset; - HeaderOffset = ImageOffset + (uint)LongHeaderPattern.Length; - } - - if (ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X00) != 0) - { - ImageOffset = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X00); - } - else if (HeaderType == QualcommPartitionHeaderType.Short) - { - ImageOffset += 0x28; - } - else - { - ImageOffset += 0x50; - } - - ImageAddress = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X04); - ImageSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X08); - CodeSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X0C); - SignatureAddress = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X10); - SignatureSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X14); - SignatureOffset = SignatureAddress - ImageAddress + ImageOffset; - CertificatesAddress = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X18); - CertificatesSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X1C); - CertificatesOffset = CertificatesAddress - ImageAddress + ImageOffset; - - uint CurrentCertificateOffset = CertificatesOffset; - uint CertificateSize = 0; - while (CurrentCertificateOffset < (CertificatesOffset + CertificatesSize)) - { - if ((Binary[CurrentCertificateOffset] == 0x30) && (Binary[CurrentCertificateOffset + 1] == 0x82)) - { - CertificateSize = (uint)(Binary[CurrentCertificateOffset + 2] * 0x100) + Binary[CurrentCertificateOffset + 3] + 4; // Big endian! - - if ((CurrentCertificateOffset + CertificateSize) == (CertificatesOffset + CertificatesSize)) - { - // This is the last certificate. So this is the root key. - RootKeyHash = new SHA256Managed().ComputeHash(Binary, (int)CurrentCertificateOffset, (int)CertificateSize); - -#if DEBUG - System.Diagnostics.Debug.Print("RKH: " + Converter.ConvertHexToString(RootKeyHash, "")); -#endif - } -#if DEBUG - else - { - System.Diagnostics.Debug.Print("Cert: " + Converter.ConvertHexToString(new SHA256Managed().ComputeHash(Binary, (int)CurrentCertificateOffset, (int)CertificateSize), "")); - } -#endif - CurrentCertificateOffset += CertificateSize; - } - else - { - if ((RootKeyHash == null) && (CurrentCertificateOffset > CertificatesOffset)) - { - CurrentCertificateOffset -= CertificateSize; - - // This is the last certificate. So this is the root key. - RootKeyHash = new SHA256Managed().ComputeHash(Binary, (int)CurrentCertificateOffset, (int)CertificateSize); - -#if DEBUG - System.Diagnostics.Debug.Print("RKH: " + Converter.ConvertHexToString(RootKeyHash, "")); -#endif - } - break; - } - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Security.Cryptography; + +namespace WPinternals +{ + internal enum QualcommPartitionHeaderType + { + Long, + Short + }; + + internal class QualcommPartition + { + internal byte[] Binary; + internal uint HeaderOffset; + internal QualcommPartitionHeaderType HeaderType; + internal uint ImageOffset; + internal uint ImageAddress; + internal uint ImageSize; + internal uint CodeSize; + internal uint SignatureAddress; + internal uint SignatureSize; + internal uint SignatureOffset; + internal uint CertificatesAddress; + internal uint CertificatesSize; + internal uint CertificatesOffset; + internal byte[] RootKeyHash = null; + + internal QualcommPartition(string Path) : this(File.ReadAllBytes(Path)) { } + + internal QualcommPartition(byte[] Binary, uint Offset = 0) + { +#if DEBUG + System.Diagnostics.Debug.Print("Loader: " + Converter.ConvertHexToString(new SHA256Managed().ComputeHash(Binary, 0, Binary.Length), "")); +#endif + + this.Binary = Binary; + + byte[] LongHeaderPattern = new byte[] { 0xD1, 0xDC, 0x4B, 0x84, 0x34, 0x10, 0xD7, 0x73, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + byte[] LongHeaderMask = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (ByteOperations.FindPattern(Binary, Offset, 4, new byte[] { 0x7F, 0x45, 0x4C, 0x46 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, null) == 0) + { + // This is an ELF image + // First program header is a reference to the elf-header + // Second program header is a reference to the signed hash-table + HeaderType = QualcommPartitionHeaderType.Short; + UInt32 ProgramHeaderOffset; + UInt16 ProgramHeaderEntrySize; + UInt32 HashTableProgramHeaderOffset; + if (Binary[Offset + 0x04] == 1) + { + // 32-bit elf image + ProgramHeaderOffset = Offset + ByteOperations.ReadUInt32(Binary, Offset + 0x1c); + ProgramHeaderEntrySize = ByteOperations.ReadUInt16(Binary, Offset + 0x2a); + HashTableProgramHeaderOffset = ProgramHeaderOffset + ProgramHeaderEntrySize; + ImageOffset = Offset + ByteOperations.ReadUInt32(Binary, HashTableProgramHeaderOffset + 0x04); + HeaderOffset = ImageOffset + 8; + } + else if (Binary[Offset + 0x04] == 2) + { + // 64-bit elf image + ProgramHeaderOffset = Offset + ByteOperations.ReadUInt32(Binary, Offset + 0x20); + ProgramHeaderEntrySize = ByteOperations.ReadUInt16(Binary, Offset + 0x36); + HashTableProgramHeaderOffset = ProgramHeaderOffset + ProgramHeaderEntrySize; + ImageOffset = Offset + (UInt32)ByteOperations.ReadUInt64(Binary, HashTableProgramHeaderOffset + 0x08); + HeaderOffset = ImageOffset + 8; + } + else + { + throw new WPinternalsException("Invalid programmer", "The type of elf image could not be determined from the provided programmer."); + } + } + else if (ByteOperations.FindPattern(Binary, Offset, (uint)LongHeaderPattern.Length, LongHeaderPattern, LongHeaderMask, null) == null) + { + HeaderType = QualcommPartitionHeaderType.Short; + ImageOffset = Offset; + HeaderOffset = ImageOffset + 8; + } + else + { + HeaderType = QualcommPartitionHeaderType.Long; + ImageOffset = Offset; + HeaderOffset = ImageOffset + (uint)LongHeaderPattern.Length; + } + + if (ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X00) != 0) + { + ImageOffset = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X00); + } + else if (HeaderType == QualcommPartitionHeaderType.Short) + { + ImageOffset += 0x28; + } + else + { + ImageOffset += 0x50; + } + + ImageAddress = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X04); + ImageSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X08); + CodeSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X0C); + SignatureAddress = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X10); + SignatureSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X14); + SignatureOffset = SignatureAddress - ImageAddress + ImageOffset; + CertificatesAddress = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X18); + CertificatesSize = ByteOperations.ReadUInt32(Binary, HeaderOffset + 0X1C); + CertificatesOffset = CertificatesAddress - ImageAddress + ImageOffset; + + uint CurrentCertificateOffset = CertificatesOffset; + uint CertificateSize = 0; + while (CurrentCertificateOffset < (CertificatesOffset + CertificatesSize)) + { + if ((Binary[CurrentCertificateOffset] == 0x30) && (Binary[CurrentCertificateOffset + 1] == 0x82)) + { + CertificateSize = (uint)(Binary[CurrentCertificateOffset + 2] * 0x100) + Binary[CurrentCertificateOffset + 3] + 4; // Big endian! + + if ((CurrentCertificateOffset + CertificateSize) == (CertificatesOffset + CertificatesSize)) + { + // This is the last certificate. So this is the root key. + RootKeyHash = new SHA256Managed().ComputeHash(Binary, (int)CurrentCertificateOffset, (int)CertificateSize); + +#if DEBUG + System.Diagnostics.Debug.Print("RKH: " + Converter.ConvertHexToString(RootKeyHash, "")); +#endif + } +#if DEBUG + else + { + System.Diagnostics.Debug.Print("Cert: " + Converter.ConvertHexToString(new SHA256Managed().ComputeHash(Binary, (int)CurrentCertificateOffset, (int)CertificateSize), "")); + } +#endif + CurrentCertificateOffset += CertificateSize; + } + else + { + if ((RootKeyHash == null) && (CurrentCertificateOffset > CertificatesOffset)) + { + CurrentCertificateOffset -= CertificateSize; + + // This is the last certificate. So this is the root key. + RootKeyHash = new SHA256Managed().ComputeHash(Binary, (int)CurrentCertificateOffset, (int)CertificateSize); + +#if DEBUG + System.Diagnostics.Debug.Print("RKH: " + Converter.ConvertHexToString(RootKeyHash, "")); +#endif + } + break; + } + } + } + } +} diff --git a/Models/QualcommSahara.cs b/WPinternals/Models/QualcommSahara.cs similarity index 97% rename from Models/QualcommSahara.cs rename to WPinternals/Models/QualcommSahara.cs index 192e84f..bfbefff 100644 --- a/Models/QualcommSahara.cs +++ b/WPinternals/Models/QualcommSahara.cs @@ -1,600 +1,600 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace WPinternals -{ - internal enum SaharaMode : uint - { - ImageTransferPending = 0x00, - ImagetransferComplete = 0x01, - MemoryDebug = 0x02, - Command = 0x03 - } - - internal delegate void ReadyHandler(); - - internal class QualcommSahara - { - private readonly QualcommSerial Serial; - - public QualcommSahara(QualcommSerial Serial) - { - Serial.EncodeCommands = false; - Serial.DecodeResponses = false; - this.Serial = Serial; - } - - public bool SendImage(string Path) - { - bool Result = true; - - LogFile.Log("Sending programmer: " + Path, LogType.FileOnly); - - int Step = 0; - UInt32 Offset = 0; - UInt32 Length = 0; - - byte[] ImageBuffer = null; - try - { - Step = 1; - byte[] Hello = Serial.GetResponse(new byte[] { 0x01, 0x00, 0x00, 0x00 }); - - // Incoming Hello packet: - // 00000001 = Hello command id - // xxxxxxxx = Length - // xxxxxxxx = Protocol version - // xxxxxxxx = Supported version - // xxxxxxxx = Max packet length - // xxxxxxxx = Expected mode - // 6 dwords reserved space - LogFile.Log("Protocol: 0x" + ByteOperations.ReadUInt32(Hello, 0x08).ToString("X8"), LogType.FileOnly); - LogFile.Log("Supported: 0x" + ByteOperations.ReadUInt32(Hello, 0x0C).ToString("X8"), LogType.FileOnly); - LogFile.Log("MaxLength: 0x" + ByteOperations.ReadUInt32(Hello, 0x10).ToString("X8"), LogType.FileOnly); - LogFile.Log("Mode: 0x" + ByteOperations.ReadUInt32(Hello, 0x14).ToString("X8"), LogType.FileOnly); - - // Packet: - // 00000002 = Hello response command id - // 00000030 = Length - // 00000002 = Protocol version - // 00000001 = Supported version - // 00000000 = Status OK - // 00000000 = Mode - // rest is reserved space - Step = 2; - byte[] HelloResponse = new byte[] { - 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 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 - }; - Serial.SendData(HelloResponse); - - Step = 3; - using FileStream FileStream = new(Path, FileMode.Open, FileAccess.Read); - while (true) - { - Step = 4; - byte[] ReadDataRequest = Serial.GetResponse(null); - UInt32 ResponseID = ByteOperations.ReadUInt32(ReadDataRequest, 0); - if (ResponseID == 4) - { - break; - } - - if (ResponseID != 3) - { - Step = 5; - throw new BadConnectionException(); - } - - Offset = ByteOperations.ReadUInt32(ReadDataRequest, 0x0C); - Length = ByteOperations.ReadUInt32(ReadDataRequest, 0x10); - if ((ImageBuffer == null) || (ImageBuffer.Length != Length)) - { - ImageBuffer = new byte[Length]; - } - - if (FileStream.Position != Offset) - { - FileStream.Seek(Offset, SeekOrigin.Begin); - } - - Step = 6; - FileStream.Read(ImageBuffer, 0, (int)Length); - - Step = 7; - Serial.SendData(ImageBuffer); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex, LogType.FileAndConsole, Step.ToString() + " 0x" + Offset.ToString("X8") + " 0x" + Length.ToString("X8")); - Result = false; - } - - if (Result) - { - LogFile.Log("Programmer loaded into phone memory", LogType.FileOnly); - } - - return Result; - } - - public bool Handshake() - { - bool Result = true; - - try - { - byte[] Hello = Serial.GetResponse(new byte[] { 0x01, 0x00, 0x00, 0x00 }); - - // Incoming Hello packet: - // 00000001 = Hello command id - // xxxxxxxx = Length - // xxxxxxxx = Protocol version - // xxxxxxxx = Supported version - // xxxxxxxx = Max packet length - // xxxxxxxx = Expected mode - // 6 dwords reserved space - LogFile.Log("Protocol: 0x" + ByteOperations.ReadUInt32(Hello, 0x08).ToString("X8"), LogType.FileOnly); - LogFile.Log("Supported: 0x" + ByteOperations.ReadUInt32(Hello, 0x0C).ToString("X8"), LogType.FileOnly); - LogFile.Log("MaxLength: 0x" + ByteOperations.ReadUInt32(Hello, 0x10).ToString("X8"), LogType.FileOnly); - LogFile.Log("Mode: 0x" + ByteOperations.ReadUInt32(Hello, 0x14).ToString("X8"), LogType.FileOnly); - - byte[] HelloResponse = new byte[] { - 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 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 - }; - - byte[] Ready = Serial.SendCommand(HelloResponse, new byte[] { 0x03, 0x00, 0x00, 0x00 }); - } - catch - { - Result = false; - } - - return Result; - } - - public void ResetSahara() - { - Serial.SendCommand(new byte[] { 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }, new byte[] { 0x08, 0x00, 0x00, 0x00 }); - } - - public bool ConnectToProgrammer(byte[] PacketFromPcToProgrammer) - { - // Behaviour of old firehose: - // Takes about 20 ms to be started. - // Then PC has to start talking to the phone. - // Behaviour of new firehose: - // After 2000 ms the firehose starts talking to the PC - // - // For the duration of 2.5 seconds we will send Hello packages - // And also wait for incoming messages - // An incoming message can be a response to our outgoing Hello packet (read incoming until "response value") - // Or it can be an incoming Hello-packet from the programmer (always 2 packets, starting with "Chip serial num") - // Sending the hello-packet can succeed immediately, or it can timeout. - // When sending succeeds, an answer should be incoming immediately to complete the handshake. - // When an incoming Hello was received, the phone still expects to receive another Hello. - - int HelloSendCount = 0; - bool HandshakeCompleted = false; - string Incoming; - do - { - Serial.SetTimeOut(200); - HelloSendCount++; - try - { - LogFile.Log("Send Hello to programmer (" + HelloSendCount.ToString() + ")", LogType.FileOnly); - Serial.SendData(PacketFromPcToProgrammer); - LogFile.Log("Hello packet accepted", LogType.FileOnly); - } - catch - { - LogFile.Log("Hello packet not accepted", LogType.FileOnly); - } - - try - { - Serial.SetTimeOut(500); - Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); - LogFile.Log("In: " + Incoming, LogType.FileOnly); - Serial.SetTimeOut(200); - if (Incoming.Contains("Chip serial num")) - { - Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); - LogFile.Log("In: " + Incoming, LogType.FileOnly); - LogFile.Log("Incoming Hello-packets received", LogType.FileOnly); - } - - while (Incoming.IndexOf("response value") < 0) - { - Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); - LogFile.Log("In: " + Incoming, LogType.FileOnly); - } - - LogFile.Log("Incoming Hello-response received", LogType.FileOnly); - - if (!Incoming.Contains("Failed to authenticate Digital Signature.")) - { - HandshakeCompleted = true; - } - else - { - LogFile.Log("Programmer failed to authenticate Digital Signature", LogType.FileOnly); - } - } - catch { } - } - while (!HandshakeCompleted && (HelloSendCount < 6)); - - return HandshakeCompleted; - } - - public bool ConnectToProgrammerInTestMode() - { - byte[] HelloPacketFromPcToProgrammer = new byte[0x20C]; - ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0, 0x57503730); - ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0x28, 0x57503730); - ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0x208, 0x57503730); - ByteOperations.WriteUInt16(HelloPacketFromPcToProgrammer, 0x48, 0x4445); - - bool HandshakeCompleted = ConnectToProgrammer(HelloPacketFromPcToProgrammer); - - if (HandshakeCompleted) - { - LogFile.Log("Handshake completed with programmer in testmode", LogType.FileOnly); - } - else - { - LogFile.Log("Handshake with programmer failed", LogType.FileOnly); - } - - return HandshakeCompleted; - } - - public async Task Reset(string ProgrammerPath) - { - bool SendImageResult = await Task.Run(() => SendImage(ProgrammerPath)); - if (!SendImageResult) - { - return false; - } - - await Task.Run(() => StartProgrammer()); - - bool Connected = await Task.Run(() => ConnectToProgrammerInTestMode()); - if (!Connected) - { - return false; - } - - LogFile.Log("Rebooting phone", LogType.FileAndConsole); - const string Command03 = ""; - LogFile.Log("Out: " + Command03, LogType.FileOnly); - Serial.SendData(Encoding.ASCII.GetBytes(Command03)); - - string Incoming; - do - { - Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); - LogFile.Log("In: " + Incoming, LogType.FileOnly); - } - while (Incoming.IndexOf("response value") < 0); - - // Workaround for problem - // SerialPort is sometimes not disposed correctly when the device is already removed. - // So explicitly dispose here - Serial.Close(); - - return true; - } - - public void SwitchMode(SaharaMode Mode) - { - byte[] SwitchModeCommand = new byte[] { 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - ByteOperations.WriteUInt32(SwitchModeCommand, 8, (UInt32)Mode); - byte[] ResponsePattern = null; - switch (Mode) - { - case SaharaMode.ImageTransferPending: - ResponsePattern = new byte[] { 0x04, 0x00, 0x00, 0x00 }; - break; - case SaharaMode.MemoryDebug: - ResponsePattern = new byte[] { 0x09, 0x00, 0x00, 0x00 }; - break; - case SaharaMode.Command: - ResponsePattern = new byte[] { 0x0B, 0x00, 0x00, 0x00 }; - break; - } - Serial.SendCommand(SwitchModeCommand, ResponsePattern); - } - - public void StartProgrammer() - { - LogFile.Log("Starting programmer", LogType.FileAndConsole); - byte[] DoneCommand = new byte[] { 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }; - bool Started = false; - int count = 0; - do - { - count++; - try - { - byte[] DoneResponse = Serial.SendCommand(DoneCommand, new byte[] { 0x06, 0x00, 0x00, 0x00 }); - Started = true; - } - catch (BadConnectionException) - { - LogFile.Log("Problem while starting programmer. Attempting again.", LogType.FileAndConsole); - } - } while (!Started && count < 3); - if (count >= 3 && !Started) - { - LogFile.Log("Maximum number of attempts to start the programmer exceeded.", LogType.FileAndConsole); - throw new BadConnectionException(); - } - LogFile.Log("Programmer being launched on phone", LogType.FileOnly); - } - - public async Task SendEdPayload(string ProgrammerPath, string PayloadPath) - { - // First, let's read the Emergency Download payload header and verify its validity - FileStream PayloadStream = File.OpenRead(PayloadPath); - - byte[] ValidReferencePayloadHeader = new byte[] { 0x45, 0x6D, 0x65, 0x72, 0x67, 0x65, 0x6E, 0x63, 0x79, 0x20, 0x50, 0x61, 0x79, 0x6C, 0x6F, 0x61, 0x64 }; - - byte[] PayloadHeader = new byte[17]; - PayloadStream.Read(PayloadHeader, 0, 17); - - bool IsValidEdPayloadImage = StructuralComparisons.StructuralEqualityComparer.Equals(PayloadHeader, ValidReferencePayloadHeader); - if (!IsValidEdPayloadImage) - { - return false; - } - - // Now let's read the information block - PayloadStream.Seek(0x64, SeekOrigin.Begin); - byte[] PayloadInformationBlock = new byte[0x64]; - - PayloadStream.Read(PayloadInformationBlock, 0, 0x64); - string buildtime = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); - - PayloadStream.Read(PayloadInformationBlock, 0, 0x64); - string builddate = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); - - PayloadStream.Read(PayloadInformationBlock, 0, 0x64); - string version = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); - - PayloadInformationBlock = new byte[0x670]; - PayloadStream.Read(PayloadInformationBlock, 0, 0x670); - string Info = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); - - // Print some information about the payload - LogFile.Log("Emerency flasher version 0.1", LogType.FileAndConsole); - LogFile.Log("Programmer information:", LogType.FileAndConsole); - LogFile.Log("Build time: " + buildtime, LogType.FileAndConsole); - LogFile.Log("Build date: " + builddate, LogType.FileAndConsole); - LogFile.Log("Version: " + version, LogType.FileAndConsole); - LogFile.Log("Info: " + Info, LogType.FileAndConsole); - - // Send the emergency programmer to the phone - bool SendImageResult = await Task.Run(() => SendImage(ProgrammerPath)); - if (!SendImageResult) - { - return false; - } - - // Start the emergency programmer on the phone - await Task.Run(() => StartProgrammer()); - - // Wait a few seconds before sending commands - LogFile.Log("Waiting...", LogType.FileAndConsole); - Thread.Sleep(2000); - LogFile.Log("Waiting...OK", LogType.FileAndConsole); - - bool Terminated = false; - bool Connected = false; - bool ProgrammerRawMode = false; - - string Incoming; - - while (!Terminated) - { - PayloadInformationBlock = new byte[0x200]; - PayloadStream.Read(PayloadInformationBlock, 0, 0x200); - string ProgrammerCommand = Encoding.ASCII.GetString(PayloadInformationBlock.Skip(0xC).ToArray()).Trim('\0'); - - LogFile.Log(ProgrammerCommand, LogType.FileAndConsole); - - byte[] PacketFromPcToProgrammer = Array.Empty(); - byte[] temp = new byte[0x200]; - - while (true) - { - if (PayloadStream.Position == PayloadStream.Length) - { - Terminated = true; - break; - } - - PayloadStream.Read(temp, 0, 0x200); - - if (temp[12] == 77 && temp[13] == 83 && temp[14] == 71 && temp[15] == 95) - { - PayloadStream.Seek(-0x200, SeekOrigin.Current); - break; - } - - PacketFromPcToProgrammer = PacketFromPcToProgrammer.Concat(temp).ToArray(); - } - - bool ExpectingReplyFromProgrammer = false; - - if (ProgrammerCommand.Contains("XML")) - { - string Outgoing = Encoding.ASCII.GetString(PacketFromPcToProgrammer).Trim('\0'); - PacketFromPcToProgrammer = Encoding.ASCII.GetBytes(Outgoing); - LogFile.Log("Out: " + Outgoing, LogType.FileAndConsole); - } - - if (!ProgrammerCommand.Contains("RAW_DATA") && !ProgrammerRawMode) - { - ExpectingReplyFromProgrammer = true; - } - - if (ProgrammerCommand.Contains("LAST") && ProgrammerRawMode) - { - ExpectingReplyFromProgrammer = true; - } - - if (ProgrammerCommand.Contains("DATA_ALL") && ProgrammerRawMode) - { - ExpectingReplyFromProgrammer = true; - } - - if (ProgrammerCommand.Contains("RAW_DATA") && !ProgrammerRawMode) - { - LogFile.Log("Phone is not in raw mode ON, leaving...", LogType.FileAndConsole); - - // Workaround for problem - // SerialPort is sometimes not disposed correctly when the device is already removed. - // So explicitly dispose here - Serial.Close(); - - LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); - PayloadStream.Dispose(); - - return false; - } - - if (!ProgrammerCommand.Contains("RAW_DATA") && ProgrammerRawMode) - { - LogFile.Log("Phone is not in raw mode ON, leaving...", LogType.FileAndConsole); - - // Workaround for problem - // SerialPort is sometimes not disposed correctly when the device is already removed. - // So explicitly dispose here - Serial.Close(); - - LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); - PayloadStream.Dispose(); - - return false; - } - - if (Connected) - { - Serial.SendData(PacketFromPcToProgrammer); - } - - if (ExpectingReplyFromProgrammer) - { - if (!Connected) - { - Connected = ConnectToProgrammer(PacketFromPcToProgrammer); - - if (Connected) - { - LogFile.Log("Handshake completed with programmer in validated image programming (VIP) mode", LogType.FileAndConsole); - } - else - { - LogFile.Log("Handshake with programmer failed", LogType.FileAndConsole); - } - - if (!Connected) - { - LogFile.Log("Phone programmer is now ignoring us, leaving...", LogType.FileAndConsole); - - // Workaround for problem - // SerialPort is sometimes not disposed correctly when the device is already removed. - // So explicitly dispose here - Serial.Close(); - - LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); - PayloadStream.Dispose(); - - return false; - } - } - else - { - do - { - Serial.SetTimeOut(500); - Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); - Serial.SetTimeOut(200); - LogFile.Log("In: " + Incoming, LogType.FileAndConsole); - } - while (Incoming.IndexOf("response value") < 0); - - if (Incoming.Contains("rawmode=\"false\"")) - { - ProgrammerRawMode = false; - LogFile.Log("Raw mode: OFF", LogType.FileAndConsole); - } - - if (Incoming.Contains("rawmode=\"true\"")) - { - ProgrammerRawMode = true; - LogFile.Log("Raw mode: ON", LogType.FileAndConsole); - } - - if (!Incoming.Contains("ACK")) - { - LogFile.Log("Phone programmer is now ignoring us, leaving...", LogType.FileAndConsole); - - // Workaround for problem - // SerialPort is sometimes not disposed correctly when the device is already removed. - // So explicitly dispose here - Serial.Close(); - - LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); - PayloadStream.Dispose(); - - return false; - } - } - } - } - - // Workaround for problem - // SerialPort is sometimes not disposed correctly when the device is already removed. - // So explicitly dispose here - Serial.Close(); - - LogFile.Log("Phone has been emergency flashed successfully!", LogType.FileAndConsole); - PayloadStream.Dispose(); - - return true; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace WPinternals +{ + internal enum SaharaMode : uint + { + ImageTransferPending = 0x00, + ImagetransferComplete = 0x01, + MemoryDebug = 0x02, + Command = 0x03 + } + + internal delegate void ReadyHandler(); + + internal class QualcommSahara + { + private readonly QualcommSerial Serial; + + public QualcommSahara(QualcommSerial Serial) + { + Serial.EncodeCommands = false; + Serial.DecodeResponses = false; + this.Serial = Serial; + } + + public bool SendImage(string Path) + { + bool Result = true; + + LogFile.Log("Sending programmer: " + Path, LogType.FileOnly); + + int Step = 0; + UInt32 Offset = 0; + UInt32 Length = 0; + + byte[] ImageBuffer = null; + try + { + Step = 1; + byte[] Hello = Serial.GetResponse(new byte[] { 0x01, 0x00, 0x00, 0x00 }); + + // Incoming Hello packet: + // 00000001 = Hello command id + // xxxxxxxx = Length + // xxxxxxxx = Protocol version + // xxxxxxxx = Supported version + // xxxxxxxx = Max packet length + // xxxxxxxx = Expected mode + // 6 dwords reserved space + LogFile.Log("Protocol: 0x" + ByteOperations.ReadUInt32(Hello, 0x08).ToString("X8"), LogType.FileOnly); + LogFile.Log("Supported: 0x" + ByteOperations.ReadUInt32(Hello, 0x0C).ToString("X8"), LogType.FileOnly); + LogFile.Log("MaxLength: 0x" + ByteOperations.ReadUInt32(Hello, 0x10).ToString("X8"), LogType.FileOnly); + LogFile.Log("Mode: 0x" + ByteOperations.ReadUInt32(Hello, 0x14).ToString("X8"), LogType.FileOnly); + + // Packet: + // 00000002 = Hello response command id + // 00000030 = Length + // 00000002 = Protocol version + // 00000001 = Supported version + // 00000000 = Status OK + // 00000000 = Mode + // rest is reserved space + Step = 2; + byte[] HelloResponse = new byte[] { + 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 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 + }; + Serial.SendData(HelloResponse); + + Step = 3; + using FileStream FileStream = new(Path, FileMode.Open, FileAccess.Read); + while (true) + { + Step = 4; + byte[] ReadDataRequest = Serial.GetResponse(null); + UInt32 ResponseID = ByteOperations.ReadUInt32(ReadDataRequest, 0); + if (ResponseID == 4) + { + break; + } + + if (ResponseID != 3) + { + Step = 5; + throw new BadConnectionException(); + } + + Offset = ByteOperations.ReadUInt32(ReadDataRequest, 0x0C); + Length = ByteOperations.ReadUInt32(ReadDataRequest, 0x10); + if ((ImageBuffer == null) || (ImageBuffer.Length != Length)) + { + ImageBuffer = new byte[Length]; + } + + if (FileStream.Position != Offset) + { + FileStream.Seek(Offset, SeekOrigin.Begin); + } + + Step = 6; + FileStream.Read(ImageBuffer, 0, (int)Length); + + Step = 7; + Serial.SendData(ImageBuffer); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex, LogType.FileAndConsole, Step.ToString() + " 0x" + Offset.ToString("X8") + " 0x" + Length.ToString("X8")); + Result = false; + } + + if (Result) + { + LogFile.Log("Programmer loaded into phone memory", LogType.FileOnly); + } + + return Result; + } + + public bool Handshake() + { + bool Result = true; + + try + { + byte[] Hello = Serial.GetResponse(new byte[] { 0x01, 0x00, 0x00, 0x00 }); + + // Incoming Hello packet: + // 00000001 = Hello command id + // xxxxxxxx = Length + // xxxxxxxx = Protocol version + // xxxxxxxx = Supported version + // xxxxxxxx = Max packet length + // xxxxxxxx = Expected mode + // 6 dwords reserved space + LogFile.Log("Protocol: 0x" + ByteOperations.ReadUInt32(Hello, 0x08).ToString("X8"), LogType.FileOnly); + LogFile.Log("Supported: 0x" + ByteOperations.ReadUInt32(Hello, 0x0C).ToString("X8"), LogType.FileOnly); + LogFile.Log("MaxLength: 0x" + ByteOperations.ReadUInt32(Hello, 0x10).ToString("X8"), LogType.FileOnly); + LogFile.Log("Mode: 0x" + ByteOperations.ReadUInt32(Hello, 0x14).ToString("X8"), LogType.FileOnly); + + byte[] HelloResponse = new byte[] { + 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 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 + }; + + byte[] Ready = Serial.SendCommand(HelloResponse, new byte[] { 0x03, 0x00, 0x00, 0x00 }); + } + catch + { + Result = false; + } + + return Result; + } + + public void ResetSahara() + { + Serial.SendCommand(new byte[] { 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }, new byte[] { 0x08, 0x00, 0x00, 0x00 }); + } + + public bool ConnectToProgrammer(byte[] PacketFromPcToProgrammer) + { + // Behaviour of old firehose: + // Takes about 20 ms to be started. + // Then PC has to start talking to the phone. + // Behaviour of new firehose: + // After 2000 ms the firehose starts talking to the PC + // + // For the duration of 2.5 seconds we will send Hello packages + // And also wait for incoming messages + // An incoming message can be a response to our outgoing Hello packet (read incoming until "response value") + // Or it can be an incoming Hello-packet from the programmer (always 2 packets, starting with "Chip serial num") + // Sending the hello-packet can succeed immediately, or it can timeout. + // When sending succeeds, an answer should be incoming immediately to complete the handshake. + // When an incoming Hello was received, the phone still expects to receive another Hello. + + int HelloSendCount = 0; + bool HandshakeCompleted = false; + string Incoming; + do + { + Serial.SetTimeOut(200); + HelloSendCount++; + try + { + LogFile.Log("Send Hello to programmer (" + HelloSendCount.ToString() + ")", LogType.FileOnly); + Serial.SendData(PacketFromPcToProgrammer); + LogFile.Log("Hello packet accepted", LogType.FileOnly); + } + catch + { + LogFile.Log("Hello packet not accepted", LogType.FileOnly); + } + + try + { + Serial.SetTimeOut(500); + Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); + LogFile.Log("In: " + Incoming, LogType.FileOnly); + Serial.SetTimeOut(200); + if (Incoming.Contains("Chip serial num")) + { + Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); + LogFile.Log("In: " + Incoming, LogType.FileOnly); + LogFile.Log("Incoming Hello-packets received", LogType.FileOnly); + } + + while (Incoming.IndexOf("response value") < 0) + { + Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); + LogFile.Log("In: " + Incoming, LogType.FileOnly); + } + + LogFile.Log("Incoming Hello-response received", LogType.FileOnly); + + if (!Incoming.Contains("Failed to authenticate Digital Signature.")) + { + HandshakeCompleted = true; + } + else + { + LogFile.Log("Programmer failed to authenticate Digital Signature", LogType.FileOnly); + } + } + catch { } + } + while (!HandshakeCompleted && (HelloSendCount < 6)); + + return HandshakeCompleted; + } + + public bool ConnectToProgrammerInTestMode() + { + byte[] HelloPacketFromPcToProgrammer = new byte[0x20C]; + ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0, 0x57503730); + ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0x28, 0x57503730); + ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0x208, 0x57503730); + ByteOperations.WriteUInt16(HelloPacketFromPcToProgrammer, 0x48, 0x4445); + + bool HandshakeCompleted = ConnectToProgrammer(HelloPacketFromPcToProgrammer); + + if (HandshakeCompleted) + { + LogFile.Log("Handshake completed with programmer in testmode", LogType.FileOnly); + } + else + { + LogFile.Log("Handshake with programmer failed", LogType.FileOnly); + } + + return HandshakeCompleted; + } + + public async Task Reset(string ProgrammerPath) + { + bool SendImageResult = await Task.Run(() => SendImage(ProgrammerPath)); + if (!SendImageResult) + { + return false; + } + + await Task.Run(() => StartProgrammer()); + + bool Connected = await Task.Run(() => ConnectToProgrammerInTestMode()); + if (!Connected) + { + return false; + } + + LogFile.Log("Rebooting phone", LogType.FileAndConsole); + const string Command03 = ""; + LogFile.Log("Out: " + Command03, LogType.FileOnly); + Serial.SendData(Encoding.ASCII.GetBytes(Command03)); + + string Incoming; + do + { + Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); + LogFile.Log("In: " + Incoming, LogType.FileOnly); + } + while (Incoming.IndexOf("response value") < 0); + + // Workaround for problem + // SerialPort is sometimes not disposed correctly when the device is already removed. + // So explicitly dispose here + Serial.Close(); + + return true; + } + + public void SwitchMode(SaharaMode Mode) + { + byte[] SwitchModeCommand = new byte[] { 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + ByteOperations.WriteUInt32(SwitchModeCommand, 8, (UInt32)Mode); + byte[] ResponsePattern = null; + switch (Mode) + { + case SaharaMode.ImageTransferPending: + ResponsePattern = new byte[] { 0x04, 0x00, 0x00, 0x00 }; + break; + case SaharaMode.MemoryDebug: + ResponsePattern = new byte[] { 0x09, 0x00, 0x00, 0x00 }; + break; + case SaharaMode.Command: + ResponsePattern = new byte[] { 0x0B, 0x00, 0x00, 0x00 }; + break; + } + Serial.SendCommand(SwitchModeCommand, ResponsePattern); + } + + public void StartProgrammer() + { + LogFile.Log("Starting programmer", LogType.FileAndConsole); + byte[] DoneCommand = new byte[] { 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }; + bool Started = false; + int count = 0; + do + { + count++; + try + { + byte[] DoneResponse = Serial.SendCommand(DoneCommand, new byte[] { 0x06, 0x00, 0x00, 0x00 }); + Started = true; + } + catch (BadConnectionException) + { + LogFile.Log("Problem while starting programmer. Attempting again.", LogType.FileAndConsole); + } + } while (!Started && count < 3); + if (count >= 3 && !Started) + { + LogFile.Log("Maximum number of attempts to start the programmer exceeded.", LogType.FileAndConsole); + throw new BadConnectionException(); + } + LogFile.Log("Programmer being launched on phone", LogType.FileOnly); + } + + public async Task SendEdPayload(string ProgrammerPath, string PayloadPath) + { + // First, let's read the Emergency Download payload header and verify its validity + FileStream PayloadStream = File.OpenRead(PayloadPath); + + byte[] ValidReferencePayloadHeader = new byte[] { 0x45, 0x6D, 0x65, 0x72, 0x67, 0x65, 0x6E, 0x63, 0x79, 0x20, 0x50, 0x61, 0x79, 0x6C, 0x6F, 0x61, 0x64 }; + + byte[] PayloadHeader = new byte[17]; + PayloadStream.Read(PayloadHeader, 0, 17); + + bool IsValidEdPayloadImage = StructuralComparisons.StructuralEqualityComparer.Equals(PayloadHeader, ValidReferencePayloadHeader); + if (!IsValidEdPayloadImage) + { + return false; + } + + // Now let's read the information block + PayloadStream.Seek(0x64, SeekOrigin.Begin); + byte[] PayloadInformationBlock = new byte[0x64]; + + PayloadStream.Read(PayloadInformationBlock, 0, 0x64); + string buildtime = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); + + PayloadStream.Read(PayloadInformationBlock, 0, 0x64); + string builddate = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); + + PayloadStream.Read(PayloadInformationBlock, 0, 0x64); + string version = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); + + PayloadInformationBlock = new byte[0x670]; + PayloadStream.Read(PayloadInformationBlock, 0, 0x670); + string Info = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); + + // Print some information about the payload + LogFile.Log("Emerency flasher version 0.1", LogType.FileAndConsole); + LogFile.Log("Programmer information:", LogType.FileAndConsole); + LogFile.Log("Build time: " + buildtime, LogType.FileAndConsole); + LogFile.Log("Build date: " + builddate, LogType.FileAndConsole); + LogFile.Log("Version: " + version, LogType.FileAndConsole); + LogFile.Log("Info: " + Info, LogType.FileAndConsole); + + // Send the emergency programmer to the phone + bool SendImageResult = await Task.Run(() => SendImage(ProgrammerPath)); + if (!SendImageResult) + { + return false; + } + + // Start the emergency programmer on the phone + await Task.Run(() => StartProgrammer()); + + // Wait a few seconds before sending commands + LogFile.Log("Waiting...", LogType.FileAndConsole); + Thread.Sleep(2000); + LogFile.Log("Waiting...OK", LogType.FileAndConsole); + + bool Terminated = false; + bool Connected = false; + bool ProgrammerRawMode = false; + + string Incoming; + + while (!Terminated) + { + PayloadInformationBlock = new byte[0x200]; + PayloadStream.Read(PayloadInformationBlock, 0, 0x200); + string ProgrammerCommand = Encoding.ASCII.GetString(PayloadInformationBlock.Skip(0xC).ToArray()).Trim('\0'); + + LogFile.Log(ProgrammerCommand, LogType.FileAndConsole); + + byte[] PacketFromPcToProgrammer = Array.Empty(); + byte[] temp = new byte[0x200]; + + while (true) + { + if (PayloadStream.Position == PayloadStream.Length) + { + Terminated = true; + break; + } + + PayloadStream.Read(temp, 0, 0x200); + + if (temp[12] == 77 && temp[13] == 83 && temp[14] == 71 && temp[15] == 95) + { + PayloadStream.Seek(-0x200, SeekOrigin.Current); + break; + } + + PacketFromPcToProgrammer = PacketFromPcToProgrammer.Concat(temp).ToArray(); + } + + bool ExpectingReplyFromProgrammer = false; + + if (ProgrammerCommand.Contains("XML")) + { + string Outgoing = Encoding.ASCII.GetString(PacketFromPcToProgrammer).Trim('\0'); + PacketFromPcToProgrammer = Encoding.ASCII.GetBytes(Outgoing); + LogFile.Log("Out: " + Outgoing, LogType.FileAndConsole); + } + + if (!ProgrammerCommand.Contains("RAW_DATA") && !ProgrammerRawMode) + { + ExpectingReplyFromProgrammer = true; + } + + if (ProgrammerCommand.Contains("LAST") && ProgrammerRawMode) + { + ExpectingReplyFromProgrammer = true; + } + + if (ProgrammerCommand.Contains("DATA_ALL") && ProgrammerRawMode) + { + ExpectingReplyFromProgrammer = true; + } + + if (ProgrammerCommand.Contains("RAW_DATA") && !ProgrammerRawMode) + { + LogFile.Log("Phone is not in raw mode ON, leaving...", LogType.FileAndConsole); + + // Workaround for problem + // SerialPort is sometimes not disposed correctly when the device is already removed. + // So explicitly dispose here + Serial.Close(); + + LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); + PayloadStream.Dispose(); + + return false; + } + + if (!ProgrammerCommand.Contains("RAW_DATA") && ProgrammerRawMode) + { + LogFile.Log("Phone is not in raw mode ON, leaving...", LogType.FileAndConsole); + + // Workaround for problem + // SerialPort is sometimes not disposed correctly when the device is already removed. + // So explicitly dispose here + Serial.Close(); + + LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); + PayloadStream.Dispose(); + + return false; + } + + if (Connected) + { + Serial.SendData(PacketFromPcToProgrammer); + } + + if (ExpectingReplyFromProgrammer) + { + if (!Connected) + { + Connected = ConnectToProgrammer(PacketFromPcToProgrammer); + + if (Connected) + { + LogFile.Log("Handshake completed with programmer in validated image programming (VIP) mode", LogType.FileAndConsole); + } + else + { + LogFile.Log("Handshake with programmer failed", LogType.FileAndConsole); + } + + if (!Connected) + { + LogFile.Log("Phone programmer is now ignoring us, leaving...", LogType.FileAndConsole); + + // Workaround for problem + // SerialPort is sometimes not disposed correctly when the device is already removed. + // So explicitly dispose here + Serial.Close(); + + LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); + PayloadStream.Dispose(); + + return false; + } + } + else + { + do + { + Serial.SetTimeOut(500); + Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); + Serial.SetTimeOut(200); + LogFile.Log("In: " + Incoming, LogType.FileAndConsole); + } + while (Incoming.IndexOf("response value") < 0); + + if (Incoming.Contains("rawmode=\"false\"")) + { + ProgrammerRawMode = false; + LogFile.Log("Raw mode: OFF", LogType.FileAndConsole); + } + + if (Incoming.Contains("rawmode=\"true\"")) + { + ProgrammerRawMode = true; + LogFile.Log("Raw mode: ON", LogType.FileAndConsole); + } + + if (!Incoming.Contains("ACK")) + { + LogFile.Log("Phone programmer is now ignoring us, leaving...", LogType.FileAndConsole); + + // Workaround for problem + // SerialPort is sometimes not disposed correctly when the device is already removed. + // So explicitly dispose here + Serial.Close(); + + LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); + PayloadStream.Dispose(); + + return false; + } + } + } + } + + // Workaround for problem + // SerialPort is sometimes not disposed correctly when the device is already removed. + // So explicitly dispose here + Serial.Close(); + + LogFile.Log("Phone has been emergency flashed successfully!", LogType.FileAndConsole); + PayloadStream.Dispose(); + + return true; + } + } +} diff --git a/Models/QualcommSerial.cs b/WPinternals/Models/QualcommSerial.cs similarity index 97% rename from Models/QualcommSerial.cs rename to WPinternals/Models/QualcommSerial.cs index 0c8f6aa..3b9e2e4 100644 --- a/Models/QualcommSerial.cs +++ b/WPinternals/Models/QualcommSerial.cs @@ -1,379 +1,379 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using MadWizard.WinUSBNet; -using System; -using System.IO.Ports; - -namespace WPinternals -{ - internal class QualcommSerial : IDisposable - { - private bool Disposed = false; - private readonly SerialPort Port = null; - private readonly USBDevice USBDevice = null; - private readonly CRC16 CRC16; - - public bool EncodeCommands = true; - public bool DecodeResponses = true; - - public QualcommSerial(string DevicePath) - { - CRC16 = new CRC16(0x1189, 0xFFFF, 0xFFFF); - - string[] DevicePathElements = DevicePath.Split(new char[] { '#' }); - if (string.Equals(DevicePathElements[3], "{86E0D1E0-8089-11D0-9CE4-08003E301F73}", StringComparison.CurrentCultureIgnoreCase)) - { - string PortName = (string)Microsoft.Win32.Registry.GetValue(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\" + DevicePathElements[1] + @"\" + DevicePathElements[2] + @"\Device Parameters", "PortName", null); - if (PortName != null) - { - Port = new SerialPort(PortName, 115200) - { - ReadTimeout = 1000, - WriteTimeout = 1000 - }; - Port.Open(); - } - } - else - { - try - { - this.USBDevice = new USBDevice(DevicePath); - } - catch { } - } - } - - public void SendData(byte[] Data) - { - byte[] FormattedData = EncodeCommands ? FormatCommand(Data) : Data; - Port?.Write(FormattedData, 0, FormattedData.Length); - if (USBDevice != null) - { - USBDevice.OutputPipe.Write(FormattedData); - } - } - - public byte[] SendCommand(byte[] Command, byte[] ResponsePattern) - { - byte[] FormattedCommand = EncodeCommands ? FormatCommand(Command) : Command; - Port?.Write(FormattedCommand, 0, FormattedCommand.Length); - if (USBDevice != null) - { - USBDevice.OutputPipe.Write(FormattedCommand); - } - - return GetResponse(ResponsePattern); - } - - internal byte[] GetResponse(byte[] ResponsePattern) - { - byte[] ResponseBuffer = new byte[0x2000]; - int Length = 0; - bool IsIncomplete = false; - - do - { - IsIncomplete = false; - - try - { - int BytesRead = 0; - - if (Port != null) - { - BytesRead = Port.Read(ResponseBuffer, Length, ResponseBuffer.Length - Length); - } - - if (USBDevice != null) - { - BytesRead = USBDevice.InputPipe.Read(ResponseBuffer); - } - - if (BytesRead == 0) - { - LogFile.Log("Emergency mode of phone is ignoring us", LogType.FileAndConsole); - throw new BadMessageException(); - } - - Length += BytesRead; - byte[] DecodedResponse; - if (DecodeResponses) - { - DecodedResponse = DecodeResponse(ResponseBuffer, (UInt32)Length); - } - else - { - DecodedResponse = new byte[Length]; - Buffer.BlockCopy(ResponseBuffer, 0, DecodedResponse, 0, Length); - } - - if (ResponsePattern != null) - { - for (int i = 0; i < ResponsePattern.Length; i++) - { - if (DecodedResponse[i] != ResponsePattern[i]) - { - byte[] LogResponse = new byte[DecodedResponse.Length < 0x10 ? DecodedResponse.Length : 0x10]; - LogFile.Log("Qualcomm serial response: " + Converter.ConvertHexToString(LogResponse, ""), LogType.FileOnly); - LogFile.Log("Expected: " + Converter.ConvertHexToString(ResponsePattern, ""), LogType.FileOnly); - throw new BadMessageException(); - } - } - } - - return DecodedResponse; - } - catch (IncompleteMessageException) - { - IsIncomplete = true; - } - catch { } // Will be rethrown as BadConnectionException - } - while (IsIncomplete); - - Port?.DiscardInBuffer(); - if (USBDevice != null) - { - USBDevice.InputPipe.Flush(); - } - - throw new BadConnectionException(); - } - - private byte[] FormatCommand(byte[] Command) - { - if ((Command == null) || (Command.Length == 0)) - { - throw new BadMessageException(); - } - - byte[] Decoded = new byte[(Command.Length * 2) + 4]; - int Length = 0; - - Decoded[Length++] = 0x7E; - - for (int i = 0; i < Command.Length; i++) - { - if ((Command[i] == 0x7D) || (Command[i] == 0x7E)) - { - Decoded[Length++] = 0x7D; - Decoded[Length++] = (byte)(Command[i] ^ 0x20); - } - else - { - Decoded[Length++] = Command[i]; - } - } - - UInt16 Checksum = CRC16.CalculateChecksum(Command); - if (((byte)(Checksum & 0xFF) == 0x7D) || ((byte)(Checksum & 0xFF) == 0x7E)) - { - Decoded[Length++] = 0x7D; - Decoded[Length++] = (byte)((Checksum & 0xFF) ^ 0x20); - } - else - { - Decoded[Length++] = (byte)(Checksum & 0xFF); - } - - if (((byte)(Checksum >> 8) == 0x7D) || ((byte)(Checksum >> 8) == 0x7E)) - { - Decoded[Length++] = 0x7D; - Decoded[Length++] = (byte)((Checksum >> 8) ^ 0x20); - } - else - { - Decoded[Length++] = (byte)(Checksum >> 8); - } - - Decoded[Length++] = 0x7E; - - if (Length > 0) - { - byte[] Result = new byte[Length]; - Buffer.BlockCopy(Decoded, 0, Result, 0, Length); - return Result; - } - else - { - return null; - } - } - - private byte[] DecodeResponse(byte[] Response, UInt32 Length) - { - if ((Response == null) || (Response.Length == 0) || (Response[0] != 0x7E)) - { - throw new BadMessageException(); - } - - UInt32 SourceLength = Length; - Length = 0; - UInt32 SourcePos = 1; - - byte[] Message = new byte[SourceLength]; - - while (SourcePos < SourceLength) - { - if (Response[SourcePos] == 0x7E) - { - break; - } - - Message[Length++] = Response[SourcePos] == 0x7D ? (byte)(Response[++SourcePos] ^ 0x20) : Response[SourcePos]; - - SourcePos++; - } - - if (SourcePos == SourceLength) - { - throw new IncompleteMessageException(); - } - - if (Length < 3) - { - throw new BadMessageException(); - } - - byte[] TrimmedMessage = new byte[Length - 2]; - Buffer.BlockCopy(Message, 0, TrimmedMessage, 0, (int)(Length - 2)); - - UInt16 Checksum = CRC16.CalculateChecksum(TrimmedMessage); - if (((byte)(Checksum & 0xFF) != Message[Length - 2]) || ((byte)(Checksum >> 8) != Message[Length - 1])) - { - throw new BadMessageException(); - } - - return TrimmedMessage; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~QualcommSerial() - { - Dispose(false); - } - - public void Close() - { - Port?.Close(); - USBDevice?.Dispose(); - } - - protected virtual void Dispose(bool disposing) - { - if (Disposed) - { - return; - } - - if (disposing) - { - // Other disposables - } - - // Clean unmanaged resources here. - Close(); - - Disposed = true; - } - - internal void SetTimeOut(int v) - { - if (USBDevice != null) - { - USBDevice.ControlPipeTimeout = v; - } - - if (Port != null) - { - Port.ReadTimeout = v; - Port.WriteTimeout = v; - } - } - } - - public class IncompleteMessageException : Exception { public IncompleteMessageException() { } public IncompleteMessageException(string message) : base(message) { } public IncompleteMessageException(string message, Exception innerException) : base(message, innerException) { } } - public class BadMessageException : Exception { public BadMessageException() { } public BadMessageException(string message) : base(message) { } public BadMessageException(string message, Exception innerException) : base(message, innerException) { } } - public class BadConnectionException : Exception { public BadConnectionException() { } public BadConnectionException(string message) : base(message) { } public BadConnectionException(string message, Exception innerException) : base(message, innerException) { } } - - public class CRC16 - { - private readonly UInt16[] ChecksumTable = - new UInt16[] { - 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, - 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, - 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, - 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, - 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, - 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, - 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, - 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, - 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, - 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, - 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, - 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, - 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, - 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, - 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, - 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, - 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, - 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, - 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, - 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, - 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, - 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, - 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, - 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, - 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, - 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, - 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, - 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, - 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, - 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, - 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, - 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 - }; - - private readonly UInt16 Seed, FinalXor; - - public CRC16(UInt16 Polynomial, UInt16 Seed, UInt16 FinalXor) - { - this.Seed = Seed; - this.FinalXor = FinalXor; - } - - public UInt16 CalculateChecksum(byte[] Bytes) - { - UInt16 Crc = Seed; - for (int i = 0; i < Bytes.Length; ++i) - { - Crc = (UInt16)((Crc >> 8) ^ ChecksumTable[(byte)(Crc ^ Bytes[i])]); // Qualcomm implementation - } - return (UInt16)(Crc ^ FinalXor); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using MadWizard.WinUSBNet; +using System; +using System.IO.Ports; + +namespace WPinternals +{ + internal class QualcommSerial : IDisposable + { + private bool Disposed = false; + private readonly SerialPort Port = null; + private readonly USBDevice USBDevice = null; + private readonly CRC16 CRC16; + + public bool EncodeCommands = true; + public bool DecodeResponses = true; + + public QualcommSerial(string DevicePath) + { + CRC16 = new CRC16(0x1189, 0xFFFF, 0xFFFF); + + string[] DevicePathElements = DevicePath.Split(new char[] { '#' }); + if (string.Equals(DevicePathElements[3], "{86E0D1E0-8089-11D0-9CE4-08003E301F73}", StringComparison.CurrentCultureIgnoreCase)) + { + string PortName = (string)Microsoft.Win32.Registry.GetValue(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\" + DevicePathElements[1] + @"\" + DevicePathElements[2] + @"\Device Parameters", "PortName", null); + if (PortName != null) + { + Port = new SerialPort(PortName, 115200) + { + ReadTimeout = 1000, + WriteTimeout = 1000 + }; + Port.Open(); + } + } + else + { + try + { + this.USBDevice = new USBDevice(DevicePath); + } + catch { } + } + } + + public void SendData(byte[] Data) + { + byte[] FormattedData = EncodeCommands ? FormatCommand(Data) : Data; + Port?.Write(FormattedData, 0, FormattedData.Length); + if (USBDevice != null) + { + USBDevice.OutputPipe.Write(FormattedData); + } + } + + public byte[] SendCommand(byte[] Command, byte[] ResponsePattern) + { + byte[] FormattedCommand = EncodeCommands ? FormatCommand(Command) : Command; + Port?.Write(FormattedCommand, 0, FormattedCommand.Length); + if (USBDevice != null) + { + USBDevice.OutputPipe.Write(FormattedCommand); + } + + return GetResponse(ResponsePattern); + } + + internal byte[] GetResponse(byte[] ResponsePattern) + { + byte[] ResponseBuffer = new byte[0x2000]; + int Length = 0; + bool IsIncomplete = false; + + do + { + IsIncomplete = false; + + try + { + int BytesRead = 0; + + if (Port != null) + { + BytesRead = Port.Read(ResponseBuffer, Length, ResponseBuffer.Length - Length); + } + + if (USBDevice != null) + { + BytesRead = USBDevice.InputPipe.Read(ResponseBuffer); + } + + if (BytesRead == 0) + { + LogFile.Log("Emergency mode of phone is ignoring us", LogType.FileAndConsole); + throw new BadMessageException(); + } + + Length += BytesRead; + byte[] DecodedResponse; + if (DecodeResponses) + { + DecodedResponse = DecodeResponse(ResponseBuffer, (UInt32)Length); + } + else + { + DecodedResponse = new byte[Length]; + Buffer.BlockCopy(ResponseBuffer, 0, DecodedResponse, 0, Length); + } + + if (ResponsePattern != null) + { + for (int i = 0; i < ResponsePattern.Length; i++) + { + if (DecodedResponse[i] != ResponsePattern[i]) + { + byte[] LogResponse = new byte[DecodedResponse.Length < 0x10 ? DecodedResponse.Length : 0x10]; + LogFile.Log("Qualcomm serial response: " + Converter.ConvertHexToString(LogResponse, ""), LogType.FileOnly); + LogFile.Log("Expected: " + Converter.ConvertHexToString(ResponsePattern, ""), LogType.FileOnly); + throw new BadMessageException(); + } + } + } + + return DecodedResponse; + } + catch (IncompleteMessageException) + { + IsIncomplete = true; + } + catch { } // Will be rethrown as BadConnectionException + } + while (IsIncomplete); + + Port?.DiscardInBuffer(); + if (USBDevice != null) + { + USBDevice.InputPipe.Flush(); + } + + throw new BadConnectionException(); + } + + private byte[] FormatCommand(byte[] Command) + { + if ((Command == null) || (Command.Length == 0)) + { + throw new BadMessageException(); + } + + byte[] Decoded = new byte[(Command.Length * 2) + 4]; + int Length = 0; + + Decoded[Length++] = 0x7E; + + for (int i = 0; i < Command.Length; i++) + { + if ((Command[i] == 0x7D) || (Command[i] == 0x7E)) + { + Decoded[Length++] = 0x7D; + Decoded[Length++] = (byte)(Command[i] ^ 0x20); + } + else + { + Decoded[Length++] = Command[i]; + } + } + + UInt16 Checksum = CRC16.CalculateChecksum(Command); + if (((byte)(Checksum & 0xFF) == 0x7D) || ((byte)(Checksum & 0xFF) == 0x7E)) + { + Decoded[Length++] = 0x7D; + Decoded[Length++] = (byte)((Checksum & 0xFF) ^ 0x20); + } + else + { + Decoded[Length++] = (byte)(Checksum & 0xFF); + } + + if (((byte)(Checksum >> 8) == 0x7D) || ((byte)(Checksum >> 8) == 0x7E)) + { + Decoded[Length++] = 0x7D; + Decoded[Length++] = (byte)((Checksum >> 8) ^ 0x20); + } + else + { + Decoded[Length++] = (byte)(Checksum >> 8); + } + + Decoded[Length++] = 0x7E; + + if (Length > 0) + { + byte[] Result = new byte[Length]; + Buffer.BlockCopy(Decoded, 0, Result, 0, Length); + return Result; + } + else + { + return null; + } + } + + private byte[] DecodeResponse(byte[] Response, UInt32 Length) + { + if ((Response == null) || (Response.Length == 0) || (Response[0] != 0x7E)) + { + throw new BadMessageException(); + } + + UInt32 SourceLength = Length; + Length = 0; + UInt32 SourcePos = 1; + + byte[] Message = new byte[SourceLength]; + + while (SourcePos < SourceLength) + { + if (Response[SourcePos] == 0x7E) + { + break; + } + + Message[Length++] = Response[SourcePos] == 0x7D ? (byte)(Response[++SourcePos] ^ 0x20) : Response[SourcePos]; + + SourcePos++; + } + + if (SourcePos == SourceLength) + { + throw new IncompleteMessageException(); + } + + if (Length < 3) + { + throw new BadMessageException(); + } + + byte[] TrimmedMessage = new byte[Length - 2]; + Buffer.BlockCopy(Message, 0, TrimmedMessage, 0, (int)(Length - 2)); + + UInt16 Checksum = CRC16.CalculateChecksum(TrimmedMessage); + if (((byte)(Checksum & 0xFF) != Message[Length - 2]) || ((byte)(Checksum >> 8) != Message[Length - 1])) + { + throw new BadMessageException(); + } + + return TrimmedMessage; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~QualcommSerial() + { + Dispose(false); + } + + public void Close() + { + Port?.Close(); + USBDevice?.Dispose(); + } + + protected virtual void Dispose(bool disposing) + { + if (Disposed) + { + return; + } + + if (disposing) + { + // Other disposables + } + + // Clean unmanaged resources here. + Close(); + + Disposed = true; + } + + internal void SetTimeOut(int v) + { + if (USBDevice != null) + { + USBDevice.ControlPipeTimeout = v; + } + + if (Port != null) + { + Port.ReadTimeout = v; + Port.WriteTimeout = v; + } + } + } + + public class IncompleteMessageException : Exception { public IncompleteMessageException() { } public IncompleteMessageException(string message) : base(message) { } public IncompleteMessageException(string message, Exception innerException) : base(message, innerException) { } } + public class BadMessageException : Exception { public BadMessageException() { } public BadMessageException(string message) : base(message) { } public BadMessageException(string message, Exception innerException) : base(message, innerException) { } } + public class BadConnectionException : Exception { public BadConnectionException() { } public BadConnectionException(string message) : base(message) { } public BadConnectionException(string message, Exception innerException) : base(message, innerException) { } } + + public class CRC16 + { + private readonly UInt16[] ChecksumTable = + new UInt16[] { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 + }; + + private readonly UInt16 Seed, FinalXor; + + public CRC16(UInt16 Polynomial, UInt16 Seed, UInt16 FinalXor) + { + this.Seed = Seed; + this.FinalXor = FinalXor; + } + + public UInt16 CalculateChecksum(byte[] Bytes) + { + UInt16 Crc = Seed; + for (int i = 0; i < Bytes.Length; ++i) + { + Crc = (UInt16)((Crc >> 8) ^ ChecksumTable[(byte)(Crc ^ Bytes[i])]); // Qualcomm implementation + } + return (UInt16)(Crc ^ FinalXor); + } + } +} diff --git a/Models/SBL1.cs b/WPinternals/Models/SBL1.cs similarity index 97% rename from Models/SBL1.cs rename to WPinternals/Models/SBL1.cs index f266e51..345f1be 100644 --- a/Models/SBL1.cs +++ b/WPinternals/Models/SBL1.cs @@ -1,119 +1,119 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class SBL1 : QualcommPartition - { - internal SBL1(byte[] Binary) : base(Binary, 0x2800) { } - - internal byte[] GenerateExtraSector(byte[] PartitionHeader) - { - UInt32? Offset = ByteOperations.FindPattern(Binary, - new byte[] { - 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, null, null); - if (Offset == null) - { - throw new BadImageFormatException(); - } - - UInt32 PartitionLoaderTableOffset = (UInt32)Offset; - - byte[] FoundPattern = new byte[0x10]; - Offset = ByteOperations.FindPattern(Binary, - new byte[] { - 0x04, 0x00, 0x9F, 0xE5, 0x28, 0x00, 0xD0, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF - }, - new byte[] { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF - }, - FoundPattern); - if (Offset == null) - { - throw new BadImageFormatException(); - } - - UInt32 SharedMemoryAddress = ByteOperations.ReadUInt32(FoundPattern, 0x0C); - UInt32 GlobalIsSecurityEnabledAddress = SharedMemoryAddress + 0x28; - - Offset = ByteOperations.FindPattern(Binary, - new byte[] { - 0x01, 0xFF, 0xA0, 0xE3, 0xFF, 0xFF, 0xA0, 0xE1, 0x1C, 0xD0, 0x8D, 0xE2, 0xF0, 0x4F, 0xBD, 0xE8, - 0x1E, 0xFF, 0x2F, 0xE1 - }, - new byte[] { - 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }, - null); - if (Offset == null) - { - throw new BadImageFormatException(); - } - - UInt32 ReturnAddress = (UInt32)Offset - ImageOffset + ImageAddress; - - byte[] Sector = new byte[0x200]; - Array.Clear(Sector, 0, 0x200); - - byte[] Content = new byte[] { - 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0xBD, 0x02, 0x00, - 0xD8, 0x01, 0x00, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xE3, 0x3C, 0x10, 0x9F, 0xE5, - 0x00, 0x00, 0xC1, 0xE5, 0x38, 0x00, 0x9F, 0xE5, 0x38, 0x10, 0x9F, 0xE5, 0x00, 0x00, 0x81, 0xE5, - 0x34, 0x10, 0x9F, 0xE5, 0x00, 0x00, 0x81, 0xE5, 0x30, 0x00, 0x9F, 0xE5, 0x20, 0x10, 0x9F, 0xE5, - 0x2C, 0x30, 0x9F, 0xE5, 0x00, 0x20, 0x90, 0xE5, 0x00, 0x20, 0x81, 0xE5, 0x04, 0x00, 0x80, 0xE2, - 0x04, 0x10, 0x81, 0xE2, 0x03, 0x00, 0x50, 0xE1, 0xF9, 0xFF, 0xFF, 0xBA, 0x14, 0xF0, 0x9F, 0xE5, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x90, 0xBF, 0x02, 0x00, 0xD0, 0xBF, 0x02, 0x00, - 0xA0, 0xBD, 0x02, 0x00, 0xA0, 0xBE, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - byte[] PartitionTypeGuid = new byte[] { - 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 - }; - - Buffer.BlockCopy(Content, 0, Sector, 0, Content.Length); - - // Overwrite first part of partition-header with model-specific header - Buffer.BlockCopy(PartitionHeader, 0, Sector, 0, PartitionHeader.Length); - - ByteOperations.WriteUInt32(Sector, 0x70, GlobalIsSecurityEnabledAddress); - ByteOperations.WriteUInt32(Sector, 0x88, ReturnAddress); - - Buffer.BlockCopy(Binary, (int)PartitionLoaderTableOffset, Sector, 0xA0, 0x50); - ByteOperations.WriteUInt32(Sector, 0xA0 + 0x30, 0); - - Buffer.BlockCopy(Binary, (int)PartitionLoaderTableOffset, Sector, 0xF0, 0x50); - ByteOperations.WriteUInt32(Sector, 0xF0 + 0x2C, 0); - ByteOperations.WriteUInt32(Sector, 0xF0 + 0x38, 0x210F0); - - Buffer.BlockCopy(PartitionTypeGuid, 0, Sector, 0x190, 0x10); - - ByteOperations.WriteUInt32(Sector, 0x1FC, 0x0002BD28); - - return Sector; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class SBL1 : QualcommPartition + { + internal SBL1(byte[] Binary) : base(Binary, 0x2800) { } + + internal byte[] GenerateExtraSector(byte[] PartitionHeader) + { + UInt32? Offset = ByteOperations.FindPattern(Binary, + new byte[] { + 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, null, null); + if (Offset == null) + { + throw new BadImageFormatException(); + } + + UInt32 PartitionLoaderTableOffset = (UInt32)Offset; + + byte[] FoundPattern = new byte[0x10]; + Offset = ByteOperations.FindPattern(Binary, + new byte[] { + 0x04, 0x00, 0x9F, 0xE5, 0x28, 0x00, 0xD0, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF + }, + new byte[] { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF + }, + FoundPattern); + if (Offset == null) + { + throw new BadImageFormatException(); + } + + UInt32 SharedMemoryAddress = ByteOperations.ReadUInt32(FoundPattern, 0x0C); + UInt32 GlobalIsSecurityEnabledAddress = SharedMemoryAddress + 0x28; + + Offset = ByteOperations.FindPattern(Binary, + new byte[] { + 0x01, 0xFF, 0xA0, 0xE3, 0xFF, 0xFF, 0xA0, 0xE1, 0x1C, 0xD0, 0x8D, 0xE2, 0xF0, 0x4F, 0xBD, 0xE8, + 0x1E, 0xFF, 0x2F, 0xE1 + }, + new byte[] { + 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }, + null); + if (Offset == null) + { + throw new BadImageFormatException(); + } + + UInt32 ReturnAddress = (UInt32)Offset - ImageOffset + ImageAddress; + + byte[] Sector = new byte[0x200]; + Array.Clear(Sector, 0, 0x200); + + byte[] Content = new byte[] { + 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0xBD, 0x02, 0x00, + 0xD8, 0x01, 0x00, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xE3, 0x3C, 0x10, 0x9F, 0xE5, + 0x00, 0x00, 0xC1, 0xE5, 0x38, 0x00, 0x9F, 0xE5, 0x38, 0x10, 0x9F, 0xE5, 0x00, 0x00, 0x81, 0xE5, + 0x34, 0x10, 0x9F, 0xE5, 0x00, 0x00, 0x81, 0xE5, 0x30, 0x00, 0x9F, 0xE5, 0x20, 0x10, 0x9F, 0xE5, + 0x2C, 0x30, 0x9F, 0xE5, 0x00, 0x20, 0x90, 0xE5, 0x00, 0x20, 0x81, 0xE5, 0x04, 0x00, 0x80, 0xE2, + 0x04, 0x10, 0x81, 0xE2, 0x03, 0x00, 0x50, 0xE1, 0xF9, 0xFF, 0xFF, 0xBA, 0x14, 0xF0, 0x9F, 0xE5, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x90, 0xBF, 0x02, 0x00, 0xD0, 0xBF, 0x02, 0x00, + 0xA0, 0xBD, 0x02, 0x00, 0xA0, 0xBE, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + byte[] PartitionTypeGuid = new byte[] { + 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 + }; + + Buffer.BlockCopy(Content, 0, Sector, 0, Content.Length); + + // Overwrite first part of partition-header with model-specific header + Buffer.BlockCopy(PartitionHeader, 0, Sector, 0, PartitionHeader.Length); + + ByteOperations.WriteUInt32(Sector, 0x70, GlobalIsSecurityEnabledAddress); + ByteOperations.WriteUInt32(Sector, 0x88, ReturnAddress); + + Buffer.BlockCopy(Binary, (int)PartitionLoaderTableOffset, Sector, 0xA0, 0x50); + ByteOperations.WriteUInt32(Sector, 0xA0 + 0x30, 0); + + Buffer.BlockCopy(Binary, (int)PartitionLoaderTableOffset, Sector, 0xF0, 0x50); + ByteOperations.WriteUInt32(Sector, 0xF0 + 0x2C, 0); + ByteOperations.WriteUInt32(Sector, 0xF0 + 0x38, 0x210F0); + + Buffer.BlockCopy(PartitionTypeGuid, 0, Sector, 0x190, 0x10); + + ByteOperations.WriteUInt32(Sector, 0x1FC, 0x0002BD28); + + return Sector; + } + } +} diff --git a/Models/SBL2.cs b/WPinternals/Models/SBL2.cs similarity index 97% rename from Models/SBL2.cs rename to WPinternals/Models/SBL2.cs index 14ef595..90973f6 100644 --- a/Models/SBL2.cs +++ b/WPinternals/Models/SBL2.cs @@ -1,56 +1,56 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class SBL2 - { - internal byte[] Binary; - - internal SBL2(byte[] Binary) - { - this.Binary = Binary; - } - - // Magic! - internal byte[] Patch() - { - UInt32? PatchOffset = ByteOperations.FindPattern(Binary, - new byte[] { - 0xFF, 0xFF, 0xFF, 0xE3, 0x01, 0x0E, 0x42, 0xE3, 0x28, 0x00, 0xD0, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1 - }, - new byte[] { - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - null); - - if (PatchOffset == null) - { - throw new BadImageFormatException(); - } - - Buffer.BlockCopy(new byte[] { 0x00, 0x00, 0xA0, 0xE3 }, 0, Binary, (int)PatchOffset + 8, 4); - - return Binary; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class SBL2 + { + internal byte[] Binary; + + internal SBL2(byte[] Binary) + { + this.Binary = Binary; + } + + // Magic! + internal byte[] Patch() + { + UInt32? PatchOffset = ByteOperations.FindPattern(Binary, + new byte[] { + 0xFF, 0xFF, 0xFF, 0xE3, 0x01, 0x0E, 0x42, 0xE3, 0x28, 0x00, 0xD0, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1 + }, + new byte[] { + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + null); + + if (PatchOffset == null) + { + throw new BadImageFormatException(); + } + + Buffer.BlockCopy(new byte[] { 0x00, 0x00, 0xA0, 0xE3 }, 0, Binary, (int)PatchOffset + 8, 4); + + return Binary; + } + } +} diff --git a/Models/SBL3.cs b/WPinternals/Models/SBL3.cs similarity index 97% rename from Models/SBL3.cs rename to WPinternals/Models/SBL3.cs index 3a41d4f..ebe7477 100644 --- a/Models/SBL3.cs +++ b/WPinternals/Models/SBL3.cs @@ -1,88 +1,88 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; - -namespace WPinternals -{ - internal class SBL3 - { - internal byte[] Binary; - - internal SBL3(byte[] Binary) - { - this.Binary = Binary; - } - - internal SBL3(string FileName) - { - Binary = null; - - // First try to parse as FFU - try - { - if (FFU.IsFFU(FileName)) - { - FFU FFUFile = new(FileName); - Binary = FFUFile.GetPartition("SBL3"); - } - } - catch { } - - // If not succeeded, then try to parse it as raw image - if (Binary == null) - { - byte[] SBL3Pattern = new byte[] { 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF }; - byte[] SBL3Mask = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF }; - - UInt32? Offset = ByteOperations.FindPatternInFile(FileName, SBL3Pattern, SBL3Mask, out byte[] SBL3Header); - - if (Offset != null) - { - UInt32 Length = ByteOperations.ReadUInt32(SBL3Header, 0x10) + 0x28; // SBL3 Image Size + Header Size - Binary = new byte[Length]; - - FileStream Stream = new(FileName, FileMode.Open, FileAccess.Read); - Stream.Seek((long)Offset, SeekOrigin.Begin); - Stream.Read(Binary, 0, (int)Length); - Stream.Close(); - } - } - } - - // Magic! - internal byte[] Patch() - { - UInt32? PatchOffset = ByteOperations.FindPattern(Binary, - new byte[] { 0x04, 0x00, 0x9F, 0xE5, 0x28, 0x00, 0xD0, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1 }, - null, null); - - if (PatchOffset == null) - { - throw new BadImageFormatException(); - } - - Buffer.BlockCopy(new byte[] { 0x00, 0x00, 0xA0, 0xE3 }, 0, Binary, (int)PatchOffset + 4, 4); - - return Binary; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; + +namespace WPinternals +{ + internal class SBL3 + { + internal byte[] Binary; + + internal SBL3(byte[] Binary) + { + this.Binary = Binary; + } + + internal SBL3(string FileName) + { + Binary = null; + + // First try to parse as FFU + try + { + if (FFU.IsFFU(FileName)) + { + FFU FFUFile = new(FileName); + Binary = FFUFile.GetPartition("SBL3"); + } + } + catch { } + + // If not succeeded, then try to parse it as raw image + if (Binary == null) + { + byte[] SBL3Pattern = new byte[] { 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF }; + byte[] SBL3Mask = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF }; + + UInt32? Offset = ByteOperations.FindPatternInFile(FileName, SBL3Pattern, SBL3Mask, out byte[] SBL3Header); + + if (Offset != null) + { + UInt32 Length = ByteOperations.ReadUInt32(SBL3Header, 0x10) + 0x28; // SBL3 Image Size + Header Size + Binary = new byte[Length]; + + FileStream Stream = new(FileName, FileMode.Open, FileAccess.Read); + Stream.Seek((long)Offset, SeekOrigin.Begin); + Stream.Read(Binary, 0, (int)Length); + Stream.Close(); + } + } + } + + // Magic! + internal byte[] Patch() + { + UInt32? PatchOffset = ByteOperations.FindPattern(Binary, + new byte[] { 0x04, 0x00, 0x9F, 0xE5, 0x28, 0x00, 0xD0, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1 }, + null, null); + + if (PatchOffset == null) + { + throw new BadImageFormatException(); + } + + Buffer.BlockCopy(new byte[] { 0x00, 0x00, 0xA0, 0xE3 }, 0, Binary, (int)PatchOffset + 4, 4); + + return Binary; + } + } +} diff --git a/Models/UEFI.cs b/WPinternals/Models/UEFI.cs similarity index 97% rename from Models/UEFI.cs rename to WPinternals/Models/UEFI.cs index f158800..54794f6 100644 --- a/Models/UEFI.cs +++ b/WPinternals/Models/UEFI.cs @@ -1,647 +1,647 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace WPinternals -{ - internal class EFI - { - internal Guid Guid; - internal string Name; - internal int Type; - internal UInt32 Size; - internal UInt32 FileOffset; - internal UInt32 SectionOffset; - internal UInt32 BinaryOffset; - } - - internal class UEFI - { - internal byte[] Binary; - private byte[] DecompressedImage; - internal List EFIs = new(); - private readonly byte PaddingByteValue = 0xFF; - private readonly UInt32 DecompressedVolumeSectionHeaderOffset; - private readonly UInt32 DecompressedVolumeHeaderOffset; - private readonly UInt32 VolumeSize; - private readonly UInt16 VolumeHeaderSize; - private readonly UInt32 FileHeaderOffset; - private readonly UInt32 SectionHeaderOffset; - private readonly UInt32 CompressedSubImageOffset; - private UInt32 CompressedSubImageSize; - - // First 0x28 bytes are Qualcomm partition header - // Inside the attributes of the VolumeHeader, the Volume-alignment is set to 8 (on Windows Phone UEFI images) - // The Volume always starts right after the Qualcomm header at position 0x28. - // So the VolumeHeader-alignment is always complied. - private readonly UInt32 VolumeHeaderOffset = 0x28; - - internal UEFI(byte[] UefiBinary) - { - Binary = UefiBinary; - - string VolumeHeaderMagic; - UInt32? Offset = ByteOperations.FindAscii(Binary, "_FVH"); - VolumeHeaderOffset = Offset == null ? throw new BadImageFormatException() : (UInt32)Offset - 0x28; - - if (!VerifyVolumeChecksum(Binary, VolumeHeaderOffset)) - { - throw new BadImageFormatException(); - } - - VolumeSize = ByteOperations.ReadUInt32(Binary, VolumeHeaderOffset + 0x20); // TODO: This is actually a QWORD - VolumeHeaderSize = ByteOperations.ReadUInt16(Binary, VolumeHeaderOffset + 0x30); - PaddingByteValue = (ByteOperations.ReadUInt32(Binary, VolumeHeaderOffset + 0x2C) & 0x00000800) > 0 ? (byte)0xFF : (byte)0x00; // EFI_FVB_ERASE_POLARITY = 0x00000800 - - // In the volume look for a file of type EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE (0x0B) - - FileHeaderOffset = VolumeHeaderOffset + VolumeHeaderSize; - bool VolumeFound = false; - int FileType; - UInt32 FileSize; - do - { - if (!VerifyFileChecksum(Binary, FileHeaderOffset)) - { - throw new BadImageFormatException(); - } - - FileType = ByteOperations.ReadUInt8(Binary, FileHeaderOffset + 0x12); - FileSize = ByteOperations.ReadUInt24(Binary, FileHeaderOffset + 0x14); - - if (FileType == 0x0B) // EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE - { - VolumeFound = true; - } - else - { - FileHeaderOffset += FileSize; - - // FileHeaderOffset in Volume-body must be Align 8 - // In the file-header-attributes the file-alignment relative to the start of the volume is always set to 1, - // so that alignment can be ignored. - FileHeaderOffset = ByteOperations.Align(VolumeHeaderOffset + VolumeHeaderSize, FileHeaderOffset, 8); - } - } - while (!VolumeFound && (FileHeaderOffset < (VolumeHeaderOffset + VolumeSize))); - - if (!VolumeFound) - { - throw new BadImageFormatException(); - } - - // Look in file for section of type EFI_SECTION_GUID_DEFINED (0x02) - - SectionHeaderOffset = FileHeaderOffset + 0x18; - int SectionType; - UInt32 SectionSize; - UInt16 SectionHeaderSize = 0; - - bool DecompressedVolumeFound = false; - do - { - SectionType = ByteOperations.ReadUInt8(Binary, SectionHeaderOffset + 0x03); - SectionSize = ByteOperations.ReadUInt24(Binary, SectionHeaderOffset + 0x00); - - if (SectionType == 0x02) // EFI_SECTION_GUID_DEFINED - { - SectionHeaderSize = ByteOperations.ReadUInt16(Binary, SectionHeaderOffset + 0x14); - DecompressedVolumeFound = true; - } - else - { - SectionHeaderOffset += SectionSize; - - // SectionHeaderOffset in File-body must be Align 4 - SectionHeaderOffset = ByteOperations.Align(FileHeaderOffset + 0x18, SectionHeaderOffset, 4); - } - } - while (!DecompressedVolumeFound && (SectionHeaderOffset < (FileHeaderOffset + FileSize))); - - if (!DecompressedVolumeFound) - { - throw new BadImageFormatException(); - } - - // Decompress subvolume - CompressedSubImageOffset = SectionHeaderOffset + SectionHeaderSize; - CompressedSubImageSize = SectionSize - SectionHeaderSize; - - // DECOMPRESS HERE - DecompressedImage = LZMA.Decompress(Binary, CompressedSubImageOffset, CompressedSubImageSize); - - // Extracted volume contains Sections at its root level - - DecompressedVolumeSectionHeaderOffset = 0; - DecompressedVolumeFound = false; - do - { - SectionType = ByteOperations.ReadUInt8(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x03); - SectionSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x00); - SectionHeaderSize = ByteOperations.ReadUInt16(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x14); - - if (SectionType == 0x17) // EFI_SECTION_FIRMWARE_VOLUME_IMAGE - { - DecompressedVolumeFound = true; - } - else - { - DecompressedVolumeSectionHeaderOffset += SectionSize; - - // SectionHeaderOffset in File-body must be Align 4 - DecompressedVolumeSectionHeaderOffset = ByteOperations.Align(FileHeaderOffset + 0x18, DecompressedVolumeSectionHeaderOffset, 4); - } - } - while (!DecompressedVolumeFound && (DecompressedVolumeSectionHeaderOffset < DecompressedImage.Length)); - - if (!DecompressedVolumeFound) - { - throw new BadImageFormatException(); - } - - DecompressedVolumeHeaderOffset = DecompressedVolumeSectionHeaderOffset + 4; - - // PARSE COMPRESSED VOLUME - VolumeHeaderMagic = ByteOperations.ReadAsciiString(DecompressedImage, DecompressedVolumeHeaderOffset + 0x28, 0x04); - if (VolumeHeaderMagic != "_FVH") - { - throw new BadImageFormatException(); - } - - if (!VerifyVolumeChecksum(DecompressedImage, DecompressedVolumeHeaderOffset)) - { - throw new BadImageFormatException(); - } - - Int32 DecompressedVolumeSize = ByteOperations.ReadInt32(DecompressedImage, DecompressedVolumeHeaderOffset + 0x20); // TODO: This is actually a QWORD - UInt16 DecompressedVolumeHeaderSize = ByteOperations.ReadUInt16(DecompressedImage, DecompressedVolumeHeaderOffset + 0x30); - - // The files in this decompressed volume are the real EFI's. - UInt32 DecompressedFileHeaderOffset = DecompressedVolumeHeaderOffset + DecompressedVolumeHeaderSize; - EFI CurrentEFI; - do - { - if ((DecompressedFileHeaderOffset + 0x18) >= (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)) - { - break; - } - - bool ContentFound = false; - for (int i = 0; i < 0x18; i++) - { - if (DecompressedImage[DecompressedFileHeaderOffset + i] != PaddingByteValue) - { - ContentFound = true; - break; - } - } - if (!ContentFound) - { - break; - } - - FileSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedFileHeaderOffset + 0x14); - - if ((DecompressedFileHeaderOffset + FileSize) >= (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)) - { - break; - } - - if (!VerifyFileChecksum(DecompressedImage, DecompressedFileHeaderOffset)) - { - throw new BadImageFormatException(); - } - - CurrentEFI = new EFI - { - Type = ByteOperations.ReadUInt8(DecompressedImage, DecompressedFileHeaderOffset + 0x12) - }; - byte[] FileGuidBytes = new byte[0x10]; - Buffer.BlockCopy(DecompressedImage, (int)DecompressedFileHeaderOffset + 0x00, FileGuidBytes, 0, 0x10); - CurrentEFI.Guid = new Guid(FileGuidBytes); - - // Parse sections of the EFI - CurrentEFI.FileOffset = DecompressedFileHeaderOffset; - UInt32 DecompressedSectionHeaderOffset = DecompressedFileHeaderOffset + 0x18; - do - { - SectionType = ByteOperations.ReadUInt8(DecompressedImage, DecompressedSectionHeaderOffset + 0x03); - SectionSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedSectionHeaderOffset + 0x00); - - // SectionTypes that are relevant here: - // 0x10 = PE File - // 0x19 = RAW - // 0x15 = Description - // Not all section headers in the UEFI specs are 4 bytes long, - // but the sections that are used in Windows Phone EFI's all have a header of 4 bytes. - if (SectionType == 0x15) - { - CurrentEFI.Name = ByteOperations.ReadUnicodeString(DecompressedImage, DecompressedSectionHeaderOffset + 0x04, SectionSize - 0x04).TrimEnd(new char[] { (char)0, ' ' }); - } - else if ((SectionType == 0x10) || (SectionType == 0x19)) - { - CurrentEFI.SectionOffset = DecompressedSectionHeaderOffset; - CurrentEFI.BinaryOffset = DecompressedSectionHeaderOffset + 0x04; - CurrentEFI.Size = SectionSize - 0x04; - } - - DecompressedSectionHeaderOffset += SectionSize; - - // SectionHeaderOffset in File-body must be Align 4 - DecompressedSectionHeaderOffset = ByteOperations.Align(DecompressedFileHeaderOffset + 0x18, DecompressedSectionHeaderOffset, 4); - } - while (DecompressedSectionHeaderOffset < (DecompressedFileHeaderOffset + FileSize)); - - DecompressedFileHeaderOffset += FileSize; - - // FileHeaderOffset in Volume-body must be Align 8 - // In the file-header-attributes the file-alignment relative to the start of the volume is always set to 1, - // so that alignment can be ignored. - DecompressedFileHeaderOffset = ByteOperations.Align(DecompressedVolumeHeaderOffset + DecompressedVolumeHeaderSize, DecompressedFileHeaderOffset, 8); - - EFIs.Add(CurrentEFI); - } - while (DecompressedFileHeaderOffset < (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)); - } - - internal byte[] GetFile(string Name) - { - EFI File = EFIs.Find(f => string.Equals(Name, f.Name, StringComparison.CurrentCultureIgnoreCase) || string.Equals(Name, f.Guid.ToString(), StringComparison.CurrentCultureIgnoreCase)); - if (File == null) - { - return null; - } - - byte[] Bytes = new byte[File.Size]; - Buffer.BlockCopy(DecompressedImage, (int)File.BinaryOffset, Bytes, 0, (int)File.Size); - - return Bytes; - } - - internal byte[] GetFile(Guid Guid) - { - EFI File = EFIs.Find(f => Guid == f.Guid); - if (File == null) - { - return null; - } - - byte[] Bytes = new byte[File.Size]; - Buffer.BlockCopy(DecompressedImage, (int)File.BinaryOffset, Bytes, 0, (int)File.Size); - - return Bytes; - } - - internal void ReplaceFile(string Name, byte[] Binary) - { - EFI File = EFIs.Find(f => string.Equals(Name, f.Name, StringComparison.CurrentCultureIgnoreCase) || string.Equals(Name, f.Guid.ToString(), StringComparison.CurrentCultureIgnoreCase)); - if (File == null) - { - throw new ArgumentOutOfRangeException(); - } - - UInt32 OldBinarySize = File.Size; - UInt32 NewBinarySize = (UInt32)Binary.Length; - - UInt32 OldSectionPadding = ByteOperations.Align(0, OldBinarySize, 4) - OldBinarySize; - UInt32 NewSectionPadding = ByteOperations.Align(0, NewBinarySize, 4) - NewBinarySize; - - UInt32 OldFileSize = ByteOperations.ReadUInt24(DecompressedImage, File.FileOffset + 0x14); - UInt32 NewFileSize = OldFileSize - OldBinarySize - OldSectionPadding + NewBinarySize + NewSectionPadding; - - UInt32 OldFilePadding = ByteOperations.Align(0, OldFileSize, 8) - OldFileSize; - UInt32 NewFilePadding = ByteOperations.Align(0, NewFileSize, 8) - NewFileSize; - - if ((OldBinarySize + OldSectionPadding) != (NewBinarySize + NewSectionPadding)) - { - byte[] NewImage = new byte[DecompressedImage.Length - OldFileSize - OldFilePadding + NewFileSize + NewFilePadding]; // Also preserve space for File-alignement here - - // Copy Volume-head and File-head - Buffer.BlockCopy(DecompressedImage, 0, NewImage, 0, (int)File.BinaryOffset); - - // Copy new binary - Buffer.BlockCopy(Binary, 0, NewImage, (int)File.BinaryOffset, Binary.Length); - - // Insert section-padding - for (int i = 0; i < NewSectionPadding; i++) - { - NewImage[File.BinaryOffset + NewBinarySize + i] = PaddingByteValue; - } - - // Copy file-tail - Buffer.BlockCopy( - DecompressedImage, - (int)(File.BinaryOffset + OldBinarySize + OldSectionPadding), - NewImage, - (int)(File.BinaryOffset + NewBinarySize + NewSectionPadding), - (int)(File.FileOffset + OldFileSize - File.BinaryOffset - OldBinarySize - OldSectionPadding)); - - // Insert file-padding - for (int i = 0; i < NewFilePadding; i++) - { - NewImage[File.BinaryOffset + NewFileSize + i] = PaddingByteValue; - } - - // Copy volume-tail - Buffer.BlockCopy( - DecompressedImage, - (int)(File.FileOffset + OldFileSize + OldFilePadding), - NewImage, - (int)(File.FileOffset + NewFileSize + NewFilePadding), - (int)(DecompressedImage.Length - File.FileOffset - OldFileSize - OldFilePadding)); - - Int32 NewOffset = (int)(NewFileSize + NewFilePadding) - (int)(OldFileSize - OldFilePadding); - - // Fix section-size - ByteOperations.WriteUInt24(NewImage, File.SectionOffset, (UInt32)(ByteOperations.ReadUInt24(NewImage, File.SectionOffset) + NewOffset)); - - // Fix file-size - ByteOperations.WriteUInt24(NewImage, File.FileOffset + 0x14, (UInt32)(ByteOperations.ReadUInt24(NewImage, File.FileOffset + 0x14) + NewOffset)); - - // Fix volume-size - TODO: This is actually a QWORD - ByteOperations.WriteUInt32(NewImage, DecompressedVolumeHeaderOffset + 0x20, (UInt32)(ByteOperations.ReadUInt32(NewImage, DecompressedVolumeHeaderOffset + 0x20) + NewOffset)); - - // Fix section-size - ByteOperations.WriteUInt24(NewImage, DecompressedVolumeSectionHeaderOffset, (UInt32)(ByteOperations.ReadUInt24(NewImage, DecompressedVolumeSectionHeaderOffset) + NewOffset)); - - DecompressedImage = NewImage; - - // Modify all sizes in EFI's - foreach (EFI CurrentFile in EFIs) - { - if (CurrentFile.FileOffset > File.FileOffset) - { - CurrentFile.FileOffset = (UInt32)(CurrentFile.FileOffset + NewOffset); - CurrentFile.SectionOffset = (UInt32)(CurrentFile.SectionOffset + NewOffset); - CurrentFile.BinaryOffset = (UInt32)(CurrentFile.BinaryOffset + NewOffset); - } - } - } - else - { - Buffer.BlockCopy(Binary, 0, DecompressedImage, (int)File.BinaryOffset, Binary.Length); - for (int i = 0; i < NewSectionPadding; i++) - { - DecompressedImage[File.BinaryOffset + Binary.Length + i] = PaddingByteValue; - } - } - - // Calculate File-checksum - CalculateFileChecksum(DecompressedImage, File.FileOffset); - - // Calculate Volume-checksum - CalculateVolumeChecksum(DecompressedImage, DecompressedVolumeHeaderOffset); - } - - internal byte[] Rebuild() - { - // The new binary will include the Qualcomm header, but not the signature and certificates, because they won't match anyway. - byte[] NewBinary = new byte[Binary.Length]; - Buffer.BlockCopy(Binary, 0, NewBinary, 0, (int)CompressedSubImageOffset); - - ByteOperations.WriteUInt32(NewBinary, 0x10, ByteOperations.ReadUInt32(NewBinary, 0x14)); // Complete image size - does not include signature and certs anymore - ByteOperations.WriteUInt32(NewBinary, 0x18, 0x00000000); // Address of signature - ByteOperations.WriteUInt32(NewBinary, 0x1C, 0x00000000); // Signature length - ByteOperations.WriteUInt32(NewBinary, 0x20, 0x00000000); // Address of certificate - ByteOperations.WriteUInt32(NewBinary, 0x24, 0x00000000); // Certificate length - - // Compress volume - byte[] NewCompressedImage = LZMA.Compress(DecompressedImage, 0, (UInt32)DecompressedImage.Length); - - // Replace compressed volume and add correct padding - // First copy new image - Buffer.BlockCopy(NewCompressedImage, 0, NewBinary, (int)CompressedSubImageOffset, NewCompressedImage.Length); - - // Determine padding - UInt32 OldSectionPadding = ByteOperations.Align(0, CompressedSubImageSize, 4) - CompressedSubImageSize; - UInt32 NewSectionPadding = ByteOperations.Align(0, (UInt32)NewCompressedImage.Length, 4) - (UInt32)NewCompressedImage.Length; - UInt32 OldFileSize = ByteOperations.ReadUInt24(Binary, FileHeaderOffset + 0x14); - - // Filesize includes fileheader. But it does not include the padding-bytes. Not even the padding bytes of the last section. - UInt32 NewFileSize; - if ((CompressedSubImageOffset + CompressedSubImageSize + OldSectionPadding) >= (FileHeaderOffset + OldFileSize)) - { - // Compressed image is the last section of this file - NewFileSize = CompressedSubImageOffset - FileHeaderOffset + (UInt32)NewCompressedImage.Length; - } - else - { - // Compressed image is NOT the last section of this file - NewFileSize = OldFileSize - CompressedSubImageSize - OldSectionPadding + (UInt32)NewCompressedImage.Length + NewSectionPadding; - } - - // Add section padding - for (int i = 0; i < NewSectionPadding; i++) - { - NewBinary[CompressedSubImageOffset + NewCompressedImage.Length + i] = PaddingByteValue; - } - - // If there are more bytes after the section padding of the compressed image, then copy the trailing sections - if (((Int32)FileHeaderOffset + OldFileSize - CompressedSubImageOffset - CompressedSubImageSize - OldSectionPadding) > 0) - { - Buffer.BlockCopy(Binary, (int)(CompressedSubImageOffset + CompressedSubImageSize + OldSectionPadding), NewBinary, - (int)(CompressedSubImageOffset + NewCompressedImage.Length + NewSectionPadding), - (int)(FileHeaderOffset + OldFileSize - CompressedSubImageOffset - CompressedSubImageSize - OldSectionPadding)); - } - - // Add file padding - // Filesize does not include last section padding or file padding - UInt32 OldFilePadding = ByteOperations.Align(0, OldFileSize, 8) - OldFileSize; - UInt32 NewFilePadding = ByteOperations.Align(0, NewFileSize, 8) - NewFileSize; - for (int i = 0; i < NewFilePadding; i++) - { - NewBinary[FileHeaderOffset + NewFileSize + i] = PaddingByteValue; - } - - if (NewCompressedImage.Length > CompressedSubImageSize) - { - Buffer.BlockCopy(Binary, (int)(FileHeaderOffset + OldFileSize + OldFilePadding), NewBinary, (int)(FileHeaderOffset + NewFileSize + NewFilePadding), - (int)(VolumeHeaderOffset + VolumeSize - FileHeaderOffset - NewFileSize - NewFilePadding)); - } - else - { - Buffer.BlockCopy(Binary, (int)(FileHeaderOffset + OldFileSize + OldFilePadding), NewBinary, (int)(FileHeaderOffset + NewFileSize + NewFilePadding), - (int)(VolumeHeaderOffset + VolumeSize - FileHeaderOffset - OldFileSize - OldFilePadding)); - for (int i = (int)(VolumeHeaderOffset + VolumeSize - OldFileSize - OldFilePadding + NewFileSize + NewFilePadding); i < VolumeHeaderOffset + VolumeSize; i++) - { - NewBinary[i] = PaddingByteValue; - } - } - CompressedSubImageSize = (UInt32)NewCompressedImage.Length; - - // Fix section - ByteOperations.WriteUInt24(NewBinary, SectionHeaderOffset, CompressedSubImageSize + ByteOperations.ReadUInt16(Binary, SectionHeaderOffset + 0x14)); - - // Fix file - ByteOperations.WriteUInt24(NewBinary, FileHeaderOffset + 0x14, NewFileSize); - CalculateFileChecksum(NewBinary, FileHeaderOffset); - - // Fix volume (volume size is fixed) - CalculateVolumeChecksum(NewBinary, VolumeHeaderOffset); - - Binary = NewBinary; - return Binary; - } - - private void CalculateVolumeChecksum(byte[] Image, UInt32 Offset) - { - UInt16 VolumeHeaderSize = ByteOperations.ReadUInt16(Image, Offset + 0x30); - ByteOperations.WriteUInt16(Image, Offset + 0x32, 0); // Clear checksum - UInt16 NewChecksum = ByteOperations.CalculateChecksum16(Image, Offset, VolumeHeaderSize); - ByteOperations.WriteUInt16(Image, Offset + 0x32, NewChecksum); - } - - private bool VerifyVolumeChecksum(byte[] Image, UInt32 Offset) - { - UInt16 VolumeHeaderSize = ByteOperations.ReadUInt16(Image, Offset + 0x30); - byte[] Header = new byte[VolumeHeaderSize]; - Buffer.BlockCopy(Image, (int)Offset, Header, 0, VolumeHeaderSize); - ByteOperations.WriteUInt16(Header, 0x32, 0); // Clear checksum - UInt16 CurrentChecksum = ByteOperations.ReadUInt16(Image, Offset + 0x32); - UInt16 NewChecksum = ByteOperations.CalculateChecksum16(Header, 0, VolumeHeaderSize); - return CurrentChecksum == NewChecksum; - } - - private void CalculateFileChecksum(byte[] Image, UInt32 Offset) - { - const UInt16 FileHeaderSize = 0x18; - UInt32 FileSize = ByteOperations.ReadUInt24(Image, Offset + 0x14); - - ByteOperations.WriteUInt16(Image, Offset + 0x10, 0); // Clear checksum - byte NewChecksum = ByteOperations.CalculateChecksum8(Image, Offset, (UInt32)FileHeaderSize - 1); - ByteOperations.WriteUInt8(Image, Offset + 0x10, NewChecksum); // File-Header checksum - - byte FileAttribs = ByteOperations.ReadUInt8(Image, Offset + 0x13); - if ((FileAttribs & 0x40) > 0) - { - // Calculate file checksum - byte CalculatedFileChecksum = ByteOperations.CalculateChecksum8(Image, Offset + FileHeaderSize, FileSize - FileHeaderSize); - ByteOperations.WriteUInt8(Image, Offset + 0x11, CalculatedFileChecksum); - } - else - { - // Fixed file checksum - ByteOperations.WriteUInt8(Image, Offset + 0x11, 0xAA); - } - } - - private bool VerifyFileChecksum(byte[] Image, UInt32 Offset) - { - // This function only checks fixed checksum-values 0x55 and 0xAA. - - const UInt16 FileHeaderSize = 0x18; - UInt32 FileSize = ByteOperations.ReadUInt24(Image, Offset + 0x14); - - byte[] Header = new byte[FileHeaderSize - 1]; - Buffer.BlockCopy(Image, (int)Offset, Header, 0, FileHeaderSize - 1); - ByteOperations.WriteUInt16(Header, 0x10, 0); // Clear checksum - byte CurrentHeaderChecksum = ByteOperations.ReadUInt8(Image, Offset + 0x10); - byte CalculatedHeaderChecksum = ByteOperations.CalculateChecksum8(Header, 0, (UInt32)FileHeaderSize - 1); - - if (CurrentHeaderChecksum != CalculatedHeaderChecksum) - { - return false; - } - - byte FileAttribs = ByteOperations.ReadUInt8(Image, Offset + 0x13); - byte CurrentFileChecksum = ByteOperations.ReadUInt8(Image, Offset + 0x11); - if ((FileAttribs & 0x40) > 0) - { - // Calculate file checksum - byte CalculatedFileChecksum = ByteOperations.CalculateChecksum8(Image, Offset + FileHeaderSize, FileSize - FileHeaderSize); - if (CurrentFileChecksum != CalculatedFileChecksum) - { - return false; - } - } - else - { - // Fixed file checksum - if ((CurrentFileChecksum != 0xAA) && (CurrentFileChecksum != 0x55)) - { - return false; - } - } - - return true; - } - - private void ClearEfiChecksum(byte[] EfiFile) - { - UInt32 PEHeaderOffset = ByteOperations.ReadUInt32(EfiFile, 0x3C); - ByteOperations.WriteUInt32(EfiFile, PEHeaderOffset + 0x58, 0); - } - - // Magic! - internal byte[] Patch() - { - byte[] SecurityDxe = GetFile("SecurityDxe"); - ClearEfiChecksum(SecurityDxe); - - UInt32? PatchOffset = ByteOperations.FindPattern(SecurityDxe, - new byte[] { 0xF0, 0x41, 0x2D, 0xE9, 0xFF, 0xFF, 0xB0, 0xE1, 0x28, 0xD0, 0x4D, 0xE2, 0xFF, 0xFF, 0xA0, 0xE1, 0x00, 0x00, 0xFF, 0x13, 0x20, 0xFF, 0xA0, 0xE3 }, - new byte[] { 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00 }, - null); - - if (PatchOffset == null) - { - throw new BadImageFormatException(); - } - - Buffer.BlockCopy(new byte[] { 0x00, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1 }, 0, SecurityDxe, (int)PatchOffset, 8); - - ReplaceFile("SecurityDxe", SecurityDxe); - - byte[] SecurityServicesDxe = GetFile("SecurityServicesDxe"); - ClearEfiChecksum(SecurityServicesDxe); - - PatchOffset = ByteOperations.FindPattern(SecurityServicesDxe, - new byte[] { 0x10, 0xFF, 0xFF, 0xE5, 0x80, 0xFF, 0x10, 0xE3, 0xFF, 0xFF, 0xFF, 0x0A }, - new byte[] { 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00 }, - null); - - if (PatchOffset == null) - { - throw new BadImageFormatException(); - } - - ByteOperations.WriteUInt8(SecurityServicesDxe, (UInt32)PatchOffset + 0x0B, 0xEA); - - PatchOffset = ByteOperations.FindPattern(SecurityServicesDxe, - new byte[] { 0x11, 0xFF, 0xFF, 0xE5, 0x40, 0xFF, 0x10, 0xE3, 0xFF, 0xFF, 0xFF, 0x0A }, - new byte[] { 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00 }, - null); - - if (PatchOffset == null) - { - throw new BadImageFormatException(); - } - - ByteOperations.WriteUInt8(SecurityServicesDxe, (UInt32)PatchOffset + 0x0B, 0xEA); - - ReplaceFile("SecurityServicesDxe", SecurityServicesDxe); - - return Rebuild(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WPinternals +{ + internal class EFI + { + internal Guid Guid; + internal string Name; + internal int Type; + internal UInt32 Size; + internal UInt32 FileOffset; + internal UInt32 SectionOffset; + internal UInt32 BinaryOffset; + } + + internal class UEFI + { + internal byte[] Binary; + private byte[] DecompressedImage; + internal List EFIs = new(); + private readonly byte PaddingByteValue = 0xFF; + private readonly UInt32 DecompressedVolumeSectionHeaderOffset; + private readonly UInt32 DecompressedVolumeHeaderOffset; + private readonly UInt32 VolumeSize; + private readonly UInt16 VolumeHeaderSize; + private readonly UInt32 FileHeaderOffset; + private readonly UInt32 SectionHeaderOffset; + private readonly UInt32 CompressedSubImageOffset; + private UInt32 CompressedSubImageSize; + + // First 0x28 bytes are Qualcomm partition header + // Inside the attributes of the VolumeHeader, the Volume-alignment is set to 8 (on Windows Phone UEFI images) + // The Volume always starts right after the Qualcomm header at position 0x28. + // So the VolumeHeader-alignment is always complied. + private readonly UInt32 VolumeHeaderOffset = 0x28; + + internal UEFI(byte[] UefiBinary) + { + Binary = UefiBinary; + + string VolumeHeaderMagic; + UInt32? Offset = ByteOperations.FindAscii(Binary, "_FVH"); + VolumeHeaderOffset = Offset == null ? throw new BadImageFormatException() : (UInt32)Offset - 0x28; + + if (!VerifyVolumeChecksum(Binary, VolumeHeaderOffset)) + { + throw new BadImageFormatException(); + } + + VolumeSize = ByteOperations.ReadUInt32(Binary, VolumeHeaderOffset + 0x20); // TODO: This is actually a QWORD + VolumeHeaderSize = ByteOperations.ReadUInt16(Binary, VolumeHeaderOffset + 0x30); + PaddingByteValue = (ByteOperations.ReadUInt32(Binary, VolumeHeaderOffset + 0x2C) & 0x00000800) > 0 ? (byte)0xFF : (byte)0x00; // EFI_FVB_ERASE_POLARITY = 0x00000800 + + // In the volume look for a file of type EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE (0x0B) + + FileHeaderOffset = VolumeHeaderOffset + VolumeHeaderSize; + bool VolumeFound = false; + int FileType; + UInt32 FileSize; + do + { + if (!VerifyFileChecksum(Binary, FileHeaderOffset)) + { + throw new BadImageFormatException(); + } + + FileType = ByteOperations.ReadUInt8(Binary, FileHeaderOffset + 0x12); + FileSize = ByteOperations.ReadUInt24(Binary, FileHeaderOffset + 0x14); + + if (FileType == 0x0B) // EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE + { + VolumeFound = true; + } + else + { + FileHeaderOffset += FileSize; + + // FileHeaderOffset in Volume-body must be Align 8 + // In the file-header-attributes the file-alignment relative to the start of the volume is always set to 1, + // so that alignment can be ignored. + FileHeaderOffset = ByteOperations.Align(VolumeHeaderOffset + VolumeHeaderSize, FileHeaderOffset, 8); + } + } + while (!VolumeFound && (FileHeaderOffset < (VolumeHeaderOffset + VolumeSize))); + + if (!VolumeFound) + { + throw new BadImageFormatException(); + } + + // Look in file for section of type EFI_SECTION_GUID_DEFINED (0x02) + + SectionHeaderOffset = FileHeaderOffset + 0x18; + int SectionType; + UInt32 SectionSize; + UInt16 SectionHeaderSize = 0; + + bool DecompressedVolumeFound = false; + do + { + SectionType = ByteOperations.ReadUInt8(Binary, SectionHeaderOffset + 0x03); + SectionSize = ByteOperations.ReadUInt24(Binary, SectionHeaderOffset + 0x00); + + if (SectionType == 0x02) // EFI_SECTION_GUID_DEFINED + { + SectionHeaderSize = ByteOperations.ReadUInt16(Binary, SectionHeaderOffset + 0x14); + DecompressedVolumeFound = true; + } + else + { + SectionHeaderOffset += SectionSize; + + // SectionHeaderOffset in File-body must be Align 4 + SectionHeaderOffset = ByteOperations.Align(FileHeaderOffset + 0x18, SectionHeaderOffset, 4); + } + } + while (!DecompressedVolumeFound && (SectionHeaderOffset < (FileHeaderOffset + FileSize))); + + if (!DecompressedVolumeFound) + { + throw new BadImageFormatException(); + } + + // Decompress subvolume + CompressedSubImageOffset = SectionHeaderOffset + SectionHeaderSize; + CompressedSubImageSize = SectionSize - SectionHeaderSize; + + // DECOMPRESS HERE + DecompressedImage = LZMA.Decompress(Binary, CompressedSubImageOffset, CompressedSubImageSize); + + // Extracted volume contains Sections at its root level + + DecompressedVolumeSectionHeaderOffset = 0; + DecompressedVolumeFound = false; + do + { + SectionType = ByteOperations.ReadUInt8(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x03); + SectionSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x00); + SectionHeaderSize = ByteOperations.ReadUInt16(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x14); + + if (SectionType == 0x17) // EFI_SECTION_FIRMWARE_VOLUME_IMAGE + { + DecompressedVolumeFound = true; + } + else + { + DecompressedVolumeSectionHeaderOffset += SectionSize; + + // SectionHeaderOffset in File-body must be Align 4 + DecompressedVolumeSectionHeaderOffset = ByteOperations.Align(FileHeaderOffset + 0x18, DecompressedVolumeSectionHeaderOffset, 4); + } + } + while (!DecompressedVolumeFound && (DecompressedVolumeSectionHeaderOffset < DecompressedImage.Length)); + + if (!DecompressedVolumeFound) + { + throw new BadImageFormatException(); + } + + DecompressedVolumeHeaderOffset = DecompressedVolumeSectionHeaderOffset + 4; + + // PARSE COMPRESSED VOLUME + VolumeHeaderMagic = ByteOperations.ReadAsciiString(DecompressedImage, DecompressedVolumeHeaderOffset + 0x28, 0x04); + if (VolumeHeaderMagic != "_FVH") + { + throw new BadImageFormatException(); + } + + if (!VerifyVolumeChecksum(DecompressedImage, DecompressedVolumeHeaderOffset)) + { + throw new BadImageFormatException(); + } + + Int32 DecompressedVolumeSize = ByteOperations.ReadInt32(DecompressedImage, DecompressedVolumeHeaderOffset + 0x20); // TODO: This is actually a QWORD + UInt16 DecompressedVolumeHeaderSize = ByteOperations.ReadUInt16(DecompressedImage, DecompressedVolumeHeaderOffset + 0x30); + + // The files in this decompressed volume are the real EFI's. + UInt32 DecompressedFileHeaderOffset = DecompressedVolumeHeaderOffset + DecompressedVolumeHeaderSize; + EFI CurrentEFI; + do + { + if ((DecompressedFileHeaderOffset + 0x18) >= (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)) + { + break; + } + + bool ContentFound = false; + for (int i = 0; i < 0x18; i++) + { + if (DecompressedImage[DecompressedFileHeaderOffset + i] != PaddingByteValue) + { + ContentFound = true; + break; + } + } + if (!ContentFound) + { + break; + } + + FileSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedFileHeaderOffset + 0x14); + + if ((DecompressedFileHeaderOffset + FileSize) >= (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)) + { + break; + } + + if (!VerifyFileChecksum(DecompressedImage, DecompressedFileHeaderOffset)) + { + throw new BadImageFormatException(); + } + + CurrentEFI = new EFI + { + Type = ByteOperations.ReadUInt8(DecompressedImage, DecompressedFileHeaderOffset + 0x12) + }; + byte[] FileGuidBytes = new byte[0x10]; + Buffer.BlockCopy(DecompressedImage, (int)DecompressedFileHeaderOffset + 0x00, FileGuidBytes, 0, 0x10); + CurrentEFI.Guid = new Guid(FileGuidBytes); + + // Parse sections of the EFI + CurrentEFI.FileOffset = DecompressedFileHeaderOffset; + UInt32 DecompressedSectionHeaderOffset = DecompressedFileHeaderOffset + 0x18; + do + { + SectionType = ByteOperations.ReadUInt8(DecompressedImage, DecompressedSectionHeaderOffset + 0x03); + SectionSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedSectionHeaderOffset + 0x00); + + // SectionTypes that are relevant here: + // 0x10 = PE File + // 0x19 = RAW + // 0x15 = Description + // Not all section headers in the UEFI specs are 4 bytes long, + // but the sections that are used in Windows Phone EFI's all have a header of 4 bytes. + if (SectionType == 0x15) + { + CurrentEFI.Name = ByteOperations.ReadUnicodeString(DecompressedImage, DecompressedSectionHeaderOffset + 0x04, SectionSize - 0x04).TrimEnd(new char[] { (char)0, ' ' }); + } + else if ((SectionType == 0x10) || (SectionType == 0x19)) + { + CurrentEFI.SectionOffset = DecompressedSectionHeaderOffset; + CurrentEFI.BinaryOffset = DecompressedSectionHeaderOffset + 0x04; + CurrentEFI.Size = SectionSize - 0x04; + } + + DecompressedSectionHeaderOffset += SectionSize; + + // SectionHeaderOffset in File-body must be Align 4 + DecompressedSectionHeaderOffset = ByteOperations.Align(DecompressedFileHeaderOffset + 0x18, DecompressedSectionHeaderOffset, 4); + } + while (DecompressedSectionHeaderOffset < (DecompressedFileHeaderOffset + FileSize)); + + DecompressedFileHeaderOffset += FileSize; + + // FileHeaderOffset in Volume-body must be Align 8 + // In the file-header-attributes the file-alignment relative to the start of the volume is always set to 1, + // so that alignment can be ignored. + DecompressedFileHeaderOffset = ByteOperations.Align(DecompressedVolumeHeaderOffset + DecompressedVolumeHeaderSize, DecompressedFileHeaderOffset, 8); + + EFIs.Add(CurrentEFI); + } + while (DecompressedFileHeaderOffset < (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)); + } + + internal byte[] GetFile(string Name) + { + EFI File = EFIs.Find(f => string.Equals(Name, f.Name, StringComparison.CurrentCultureIgnoreCase) || string.Equals(Name, f.Guid.ToString(), StringComparison.CurrentCultureIgnoreCase)); + if (File == null) + { + return null; + } + + byte[] Bytes = new byte[File.Size]; + Buffer.BlockCopy(DecompressedImage, (int)File.BinaryOffset, Bytes, 0, (int)File.Size); + + return Bytes; + } + + internal byte[] GetFile(Guid Guid) + { + EFI File = EFIs.Find(f => Guid == f.Guid); + if (File == null) + { + return null; + } + + byte[] Bytes = new byte[File.Size]; + Buffer.BlockCopy(DecompressedImage, (int)File.BinaryOffset, Bytes, 0, (int)File.Size); + + return Bytes; + } + + internal void ReplaceFile(string Name, byte[] Binary) + { + EFI File = EFIs.Find(f => string.Equals(Name, f.Name, StringComparison.CurrentCultureIgnoreCase) || string.Equals(Name, f.Guid.ToString(), StringComparison.CurrentCultureIgnoreCase)); + if (File == null) + { + throw new ArgumentOutOfRangeException(); + } + + UInt32 OldBinarySize = File.Size; + UInt32 NewBinarySize = (UInt32)Binary.Length; + + UInt32 OldSectionPadding = ByteOperations.Align(0, OldBinarySize, 4) - OldBinarySize; + UInt32 NewSectionPadding = ByteOperations.Align(0, NewBinarySize, 4) - NewBinarySize; + + UInt32 OldFileSize = ByteOperations.ReadUInt24(DecompressedImage, File.FileOffset + 0x14); + UInt32 NewFileSize = OldFileSize - OldBinarySize - OldSectionPadding + NewBinarySize + NewSectionPadding; + + UInt32 OldFilePadding = ByteOperations.Align(0, OldFileSize, 8) - OldFileSize; + UInt32 NewFilePadding = ByteOperations.Align(0, NewFileSize, 8) - NewFileSize; + + if ((OldBinarySize + OldSectionPadding) != (NewBinarySize + NewSectionPadding)) + { + byte[] NewImage = new byte[DecompressedImage.Length - OldFileSize - OldFilePadding + NewFileSize + NewFilePadding]; // Also preserve space for File-alignement here + + // Copy Volume-head and File-head + Buffer.BlockCopy(DecompressedImage, 0, NewImage, 0, (int)File.BinaryOffset); + + // Copy new binary + Buffer.BlockCopy(Binary, 0, NewImage, (int)File.BinaryOffset, Binary.Length); + + // Insert section-padding + for (int i = 0; i < NewSectionPadding; i++) + { + NewImage[File.BinaryOffset + NewBinarySize + i] = PaddingByteValue; + } + + // Copy file-tail + Buffer.BlockCopy( + DecompressedImage, + (int)(File.BinaryOffset + OldBinarySize + OldSectionPadding), + NewImage, + (int)(File.BinaryOffset + NewBinarySize + NewSectionPadding), + (int)(File.FileOffset + OldFileSize - File.BinaryOffset - OldBinarySize - OldSectionPadding)); + + // Insert file-padding + for (int i = 0; i < NewFilePadding; i++) + { + NewImage[File.BinaryOffset + NewFileSize + i] = PaddingByteValue; + } + + // Copy volume-tail + Buffer.BlockCopy( + DecompressedImage, + (int)(File.FileOffset + OldFileSize + OldFilePadding), + NewImage, + (int)(File.FileOffset + NewFileSize + NewFilePadding), + (int)(DecompressedImage.Length - File.FileOffset - OldFileSize - OldFilePadding)); + + Int32 NewOffset = (int)(NewFileSize + NewFilePadding) - (int)(OldFileSize - OldFilePadding); + + // Fix section-size + ByteOperations.WriteUInt24(NewImage, File.SectionOffset, (UInt32)(ByteOperations.ReadUInt24(NewImage, File.SectionOffset) + NewOffset)); + + // Fix file-size + ByteOperations.WriteUInt24(NewImage, File.FileOffset + 0x14, (UInt32)(ByteOperations.ReadUInt24(NewImage, File.FileOffset + 0x14) + NewOffset)); + + // Fix volume-size - TODO: This is actually a QWORD + ByteOperations.WriteUInt32(NewImage, DecompressedVolumeHeaderOffset + 0x20, (UInt32)(ByteOperations.ReadUInt32(NewImage, DecompressedVolumeHeaderOffset + 0x20) + NewOffset)); + + // Fix section-size + ByteOperations.WriteUInt24(NewImage, DecompressedVolumeSectionHeaderOffset, (UInt32)(ByteOperations.ReadUInt24(NewImage, DecompressedVolumeSectionHeaderOffset) + NewOffset)); + + DecompressedImage = NewImage; + + // Modify all sizes in EFI's + foreach (EFI CurrentFile in EFIs) + { + if (CurrentFile.FileOffset > File.FileOffset) + { + CurrentFile.FileOffset = (UInt32)(CurrentFile.FileOffset + NewOffset); + CurrentFile.SectionOffset = (UInt32)(CurrentFile.SectionOffset + NewOffset); + CurrentFile.BinaryOffset = (UInt32)(CurrentFile.BinaryOffset + NewOffset); + } + } + } + else + { + Buffer.BlockCopy(Binary, 0, DecompressedImage, (int)File.BinaryOffset, Binary.Length); + for (int i = 0; i < NewSectionPadding; i++) + { + DecompressedImage[File.BinaryOffset + Binary.Length + i] = PaddingByteValue; + } + } + + // Calculate File-checksum + CalculateFileChecksum(DecompressedImage, File.FileOffset); + + // Calculate Volume-checksum + CalculateVolumeChecksum(DecompressedImage, DecompressedVolumeHeaderOffset); + } + + internal byte[] Rebuild() + { + // The new binary will include the Qualcomm header, but not the signature and certificates, because they won't match anyway. + byte[] NewBinary = new byte[Binary.Length]; + Buffer.BlockCopy(Binary, 0, NewBinary, 0, (int)CompressedSubImageOffset); + + ByteOperations.WriteUInt32(NewBinary, 0x10, ByteOperations.ReadUInt32(NewBinary, 0x14)); // Complete image size - does not include signature and certs anymore + ByteOperations.WriteUInt32(NewBinary, 0x18, 0x00000000); // Address of signature + ByteOperations.WriteUInt32(NewBinary, 0x1C, 0x00000000); // Signature length + ByteOperations.WriteUInt32(NewBinary, 0x20, 0x00000000); // Address of certificate + ByteOperations.WriteUInt32(NewBinary, 0x24, 0x00000000); // Certificate length + + // Compress volume + byte[] NewCompressedImage = LZMA.Compress(DecompressedImage, 0, (UInt32)DecompressedImage.Length); + + // Replace compressed volume and add correct padding + // First copy new image + Buffer.BlockCopy(NewCompressedImage, 0, NewBinary, (int)CompressedSubImageOffset, NewCompressedImage.Length); + + // Determine padding + UInt32 OldSectionPadding = ByteOperations.Align(0, CompressedSubImageSize, 4) - CompressedSubImageSize; + UInt32 NewSectionPadding = ByteOperations.Align(0, (UInt32)NewCompressedImage.Length, 4) - (UInt32)NewCompressedImage.Length; + UInt32 OldFileSize = ByteOperations.ReadUInt24(Binary, FileHeaderOffset + 0x14); + + // Filesize includes fileheader. But it does not include the padding-bytes. Not even the padding bytes of the last section. + UInt32 NewFileSize; + if ((CompressedSubImageOffset + CompressedSubImageSize + OldSectionPadding) >= (FileHeaderOffset + OldFileSize)) + { + // Compressed image is the last section of this file + NewFileSize = CompressedSubImageOffset - FileHeaderOffset + (UInt32)NewCompressedImage.Length; + } + else + { + // Compressed image is NOT the last section of this file + NewFileSize = OldFileSize - CompressedSubImageSize - OldSectionPadding + (UInt32)NewCompressedImage.Length + NewSectionPadding; + } + + // Add section padding + for (int i = 0; i < NewSectionPadding; i++) + { + NewBinary[CompressedSubImageOffset + NewCompressedImage.Length + i] = PaddingByteValue; + } + + // If there are more bytes after the section padding of the compressed image, then copy the trailing sections + if (((Int32)FileHeaderOffset + OldFileSize - CompressedSubImageOffset - CompressedSubImageSize - OldSectionPadding) > 0) + { + Buffer.BlockCopy(Binary, (int)(CompressedSubImageOffset + CompressedSubImageSize + OldSectionPadding), NewBinary, + (int)(CompressedSubImageOffset + NewCompressedImage.Length + NewSectionPadding), + (int)(FileHeaderOffset + OldFileSize - CompressedSubImageOffset - CompressedSubImageSize - OldSectionPadding)); + } + + // Add file padding + // Filesize does not include last section padding or file padding + UInt32 OldFilePadding = ByteOperations.Align(0, OldFileSize, 8) - OldFileSize; + UInt32 NewFilePadding = ByteOperations.Align(0, NewFileSize, 8) - NewFileSize; + for (int i = 0; i < NewFilePadding; i++) + { + NewBinary[FileHeaderOffset + NewFileSize + i] = PaddingByteValue; + } + + if (NewCompressedImage.Length > CompressedSubImageSize) + { + Buffer.BlockCopy(Binary, (int)(FileHeaderOffset + OldFileSize + OldFilePadding), NewBinary, (int)(FileHeaderOffset + NewFileSize + NewFilePadding), + (int)(VolumeHeaderOffset + VolumeSize - FileHeaderOffset - NewFileSize - NewFilePadding)); + } + else + { + Buffer.BlockCopy(Binary, (int)(FileHeaderOffset + OldFileSize + OldFilePadding), NewBinary, (int)(FileHeaderOffset + NewFileSize + NewFilePadding), + (int)(VolumeHeaderOffset + VolumeSize - FileHeaderOffset - OldFileSize - OldFilePadding)); + for (int i = (int)(VolumeHeaderOffset + VolumeSize - OldFileSize - OldFilePadding + NewFileSize + NewFilePadding); i < VolumeHeaderOffset + VolumeSize; i++) + { + NewBinary[i] = PaddingByteValue; + } + } + CompressedSubImageSize = (UInt32)NewCompressedImage.Length; + + // Fix section + ByteOperations.WriteUInt24(NewBinary, SectionHeaderOffset, CompressedSubImageSize + ByteOperations.ReadUInt16(Binary, SectionHeaderOffset + 0x14)); + + // Fix file + ByteOperations.WriteUInt24(NewBinary, FileHeaderOffset + 0x14, NewFileSize); + CalculateFileChecksum(NewBinary, FileHeaderOffset); + + // Fix volume (volume size is fixed) + CalculateVolumeChecksum(NewBinary, VolumeHeaderOffset); + + Binary = NewBinary; + return Binary; + } + + private void CalculateVolumeChecksum(byte[] Image, UInt32 Offset) + { + UInt16 VolumeHeaderSize = ByteOperations.ReadUInt16(Image, Offset + 0x30); + ByteOperations.WriteUInt16(Image, Offset + 0x32, 0); // Clear checksum + UInt16 NewChecksum = ByteOperations.CalculateChecksum16(Image, Offset, VolumeHeaderSize); + ByteOperations.WriteUInt16(Image, Offset + 0x32, NewChecksum); + } + + private bool VerifyVolumeChecksum(byte[] Image, UInt32 Offset) + { + UInt16 VolumeHeaderSize = ByteOperations.ReadUInt16(Image, Offset + 0x30); + byte[] Header = new byte[VolumeHeaderSize]; + Buffer.BlockCopy(Image, (int)Offset, Header, 0, VolumeHeaderSize); + ByteOperations.WriteUInt16(Header, 0x32, 0); // Clear checksum + UInt16 CurrentChecksum = ByteOperations.ReadUInt16(Image, Offset + 0x32); + UInt16 NewChecksum = ByteOperations.CalculateChecksum16(Header, 0, VolumeHeaderSize); + return CurrentChecksum == NewChecksum; + } + + private void CalculateFileChecksum(byte[] Image, UInt32 Offset) + { + const UInt16 FileHeaderSize = 0x18; + UInt32 FileSize = ByteOperations.ReadUInt24(Image, Offset + 0x14); + + ByteOperations.WriteUInt16(Image, Offset + 0x10, 0); // Clear checksum + byte NewChecksum = ByteOperations.CalculateChecksum8(Image, Offset, (UInt32)FileHeaderSize - 1); + ByteOperations.WriteUInt8(Image, Offset + 0x10, NewChecksum); // File-Header checksum + + byte FileAttribs = ByteOperations.ReadUInt8(Image, Offset + 0x13); + if ((FileAttribs & 0x40) > 0) + { + // Calculate file checksum + byte CalculatedFileChecksum = ByteOperations.CalculateChecksum8(Image, Offset + FileHeaderSize, FileSize - FileHeaderSize); + ByteOperations.WriteUInt8(Image, Offset + 0x11, CalculatedFileChecksum); + } + else + { + // Fixed file checksum + ByteOperations.WriteUInt8(Image, Offset + 0x11, 0xAA); + } + } + + private bool VerifyFileChecksum(byte[] Image, UInt32 Offset) + { + // This function only checks fixed checksum-values 0x55 and 0xAA. + + const UInt16 FileHeaderSize = 0x18; + UInt32 FileSize = ByteOperations.ReadUInt24(Image, Offset + 0x14); + + byte[] Header = new byte[FileHeaderSize - 1]; + Buffer.BlockCopy(Image, (int)Offset, Header, 0, FileHeaderSize - 1); + ByteOperations.WriteUInt16(Header, 0x10, 0); // Clear checksum + byte CurrentHeaderChecksum = ByteOperations.ReadUInt8(Image, Offset + 0x10); + byte CalculatedHeaderChecksum = ByteOperations.CalculateChecksum8(Header, 0, (UInt32)FileHeaderSize - 1); + + if (CurrentHeaderChecksum != CalculatedHeaderChecksum) + { + return false; + } + + byte FileAttribs = ByteOperations.ReadUInt8(Image, Offset + 0x13); + byte CurrentFileChecksum = ByteOperations.ReadUInt8(Image, Offset + 0x11); + if ((FileAttribs & 0x40) > 0) + { + // Calculate file checksum + byte CalculatedFileChecksum = ByteOperations.CalculateChecksum8(Image, Offset + FileHeaderSize, FileSize - FileHeaderSize); + if (CurrentFileChecksum != CalculatedFileChecksum) + { + return false; + } + } + else + { + // Fixed file checksum + if ((CurrentFileChecksum != 0xAA) && (CurrentFileChecksum != 0x55)) + { + return false; + } + } + + return true; + } + + private void ClearEfiChecksum(byte[] EfiFile) + { + UInt32 PEHeaderOffset = ByteOperations.ReadUInt32(EfiFile, 0x3C); + ByteOperations.WriteUInt32(EfiFile, PEHeaderOffset + 0x58, 0); + } + + // Magic! + internal byte[] Patch() + { + byte[] SecurityDxe = GetFile("SecurityDxe"); + ClearEfiChecksum(SecurityDxe); + + UInt32? PatchOffset = ByteOperations.FindPattern(SecurityDxe, + new byte[] { 0xF0, 0x41, 0x2D, 0xE9, 0xFF, 0xFF, 0xB0, 0xE1, 0x28, 0xD0, 0x4D, 0xE2, 0xFF, 0xFF, 0xA0, 0xE1, 0x00, 0x00, 0xFF, 0x13, 0x20, 0xFF, 0xA0, 0xE3 }, + new byte[] { 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00 }, + null); + + if (PatchOffset == null) + { + throw new BadImageFormatException(); + } + + Buffer.BlockCopy(new byte[] { 0x00, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1 }, 0, SecurityDxe, (int)PatchOffset, 8); + + ReplaceFile("SecurityDxe", SecurityDxe); + + byte[] SecurityServicesDxe = GetFile("SecurityServicesDxe"); + ClearEfiChecksum(SecurityServicesDxe); + + PatchOffset = ByteOperations.FindPattern(SecurityServicesDxe, + new byte[] { 0x10, 0xFF, 0xFF, 0xE5, 0x80, 0xFF, 0x10, 0xE3, 0xFF, 0xFF, 0xFF, 0x0A }, + new byte[] { 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00 }, + null); + + if (PatchOffset == null) + { + throw new BadImageFormatException(); + } + + ByteOperations.WriteUInt8(SecurityServicesDxe, (UInt32)PatchOffset + 0x0B, 0xEA); + + PatchOffset = ByteOperations.FindPattern(SecurityServicesDxe, + new byte[] { 0x11, 0xFF, 0xFF, 0xE5, 0x40, 0xFF, 0x10, 0xE3, 0xFF, 0xFF, 0xFF, 0x0A }, + new byte[] { 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00 }, + null); + + if (PatchOffset == null) + { + throw new BadImageFormatException(); + } + + ByteOperations.WriteUInt8(SecurityServicesDxe, (UInt32)PatchOffset + 0x0B, 0xEA); + + ReplaceFile("SecurityServicesDxe", SecurityServicesDxe); + + return Rebuild(); + } + } +} diff --git a/PatchDefinitions.xml b/WPinternals/PatchDefinitions.xml similarity index 98% rename from PatchDefinitions.xml rename to WPinternals/PatchDefinitions.xml index 1bb28b5..abdf64d 100644 --- a/PatchDefinitions.xml +++ b/WPinternals/PatchDefinitions.xml @@ -1,5430 +1,5430 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PhoneReboot.png b/WPinternals/PhoneReboot.png similarity index 100% rename from PhoneReboot.png rename to WPinternals/PhoneReboot.png diff --git a/Properties/AssemblyInfo.cs b/WPinternals/Properties/AssemblyInfo.cs similarity index 97% rename from Properties/AssemblyInfo.cs rename to WPinternals/Properties/AssemblyInfo.cs index 1b1ecdf..15fce91 100644 --- a/Properties/AssemblyInfo.cs +++ b/WPinternals/Properties/AssemblyInfo.cs @@ -1,51 +1,51 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using System.Windows; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("WPinternals")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WPinternals")] -[assembly: AssemblyCopyright("Copyright © 2018-2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -//In order to begin building localizable applications, set -//CultureYouAreCodingWith in your .csproj file -//inside a . For example, if you are using US english -//in your source files, set the to en-US. Then uncomment -//the NeutralResourceLanguage attribute below. Update the "en-US" in -//the line below to match the UICulture setting in the project file. - -//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.9.*")] -// [assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WPinternals")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WPinternals")] +[assembly: AssemblyCopyright("Copyright © 2018-2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.9.*")] +// [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Properties/PublishProfiles/FolderProfile.pubxml.user b/WPinternals/Properties/PublishProfiles/FolderProfile.pubxml.user similarity index 100% rename from Properties/PublishProfiles/FolderProfile.pubxml.user rename to WPinternals/Properties/PublishProfiles/FolderProfile.pubxml.user diff --git a/Properties/Resources.Designer.cs b/WPinternals/Properties/Resources.Designer.cs similarity index 97% rename from Properties/Resources.Designer.cs rename to WPinternals/Properties/Resources.Designer.cs index f03dbb4..53a9b4a 100644 --- a/Properties/Resources.Designer.cs +++ b/WPinternals/Properties/Resources.Designer.cs @@ -1,63 +1,63 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace WPinternals.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WPinternals.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WPinternals.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WPinternals.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/WPinternals/Properties/Resources.resx similarity index 97% rename from Properties/Resources.resx rename to WPinternals/Properties/Resources.resx index ffecec8..af7dbeb 100644 --- a/Properties/Resources.resx +++ b/WPinternals/Properties/Resources.resx @@ -1,117 +1,117 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/WPinternals/Properties/Settings.Designer.cs similarity index 97% rename from Properties/Settings.Designer.cs rename to WPinternals/Properties/Settings.Designer.cs index 9c0c94f..d01fe5c 100644 --- a/Properties/Settings.Designer.cs +++ b/WPinternals/Properties/Settings.Designer.cs @@ -1,26 +1,26 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace WPinternals.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WPinternals.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/WPinternals/Properties/Settings.settings similarity index 97% rename from Properties/Settings.settings rename to WPinternals/Properties/Settings.settings index 8f2fd95..033d7a5 100644 --- a/Properties/Settings.settings +++ b/WPinternals/Properties/Settings.settings @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/SB b/WPinternals/SB similarity index 100% rename from SB rename to WPinternals/SB diff --git a/SBA b/WPinternals/SBA similarity index 100% rename from SBA rename to WPinternals/SBA diff --git a/SBMSM b/WPinternals/SBMSM similarity index 100% rename from SBMSM rename to WPinternals/SBMSM diff --git a/Terminal.cs b/WPinternals/Terminal.cs similarity index 97% rename from Terminal.cs rename to WPinternals/Terminal.cs index 79b51cf..df55734 100644 --- a/Terminal.cs +++ b/WPinternals/Terminal.cs @@ -1,68 +1,68 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; - -namespace WPinternals -{ - internal static class Terminal - { - public static TerminalResponse Parse(byte[] Buffer, int Offset) - { - TerminalResponse Response = new(); - - // Get root node - if (Buffer.Length >= (Offset + 8)) - { - int NodeNumber = BitConverter.ToInt32(Buffer, Offset); - int NodeSize = BitConverter.ToInt32(Buffer, Offset + 4); - int End = NodeSize + Offset + 8; - int Index = Offset + 8; - if ((NodeNumber == 0x10000) && (End <= Buffer.Length)) - { - // Get subnodes - while (Index < End) - { - NodeNumber = BitConverter.ToInt32(Buffer, Index); - NodeSize = BitConverter.ToInt32(Buffer, Index + 4); - byte[] Raw = new byte[NodeSize]; - Array.Copy(Buffer, Index + 8, Raw, 0, NodeSize); - Response.RawEntries.Add(NodeNumber, Raw); - Index += NodeSize + 8; - } - } - } - - // Parse subnodes - Response.RawEntries.TryGetValue(3, out Response.PublicId); - Response.RawEntries.TryGetValue(7, out Response.RootKeyHash); - - return Response; - } - } - - internal class TerminalResponse - { - public Dictionary RawEntries = new(); - public byte[] PublicId = null; - public byte[] RootKeyHash = null; - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; + +namespace WPinternals +{ + internal static class Terminal + { + public static TerminalResponse Parse(byte[] Buffer, int Offset) + { + TerminalResponse Response = new(); + + // Get root node + if (Buffer.Length >= (Offset + 8)) + { + int NodeNumber = BitConverter.ToInt32(Buffer, Offset); + int NodeSize = BitConverter.ToInt32(Buffer, Offset + 4); + int End = NodeSize + Offset + 8; + int Index = Offset + 8; + if ((NodeNumber == 0x10000) && (End <= Buffer.Length)) + { + // Get subnodes + while (Index < End) + { + NodeNumber = BitConverter.ToInt32(Buffer, Index); + NodeSize = BitConverter.ToInt32(Buffer, Index + 4); + byte[] Raw = new byte[NodeSize]; + Array.Copy(Buffer, Index + 8, Raw, 0, NodeSize); + Response.RawEntries.Add(NodeNumber, Raw); + Index += NodeSize + 8; + } + } + } + + // Parse subnodes + Response.RawEntries.TryGetValue(3, out Response.PublicId); + Response.RawEntries.TryGetValue(7, out Response.RootKeyHash); + + return Response; + } + } + + internal class TerminalResponse + { + public Dictionary RawEntries = new(); + public byte[] PublicId = null; + public byte[] RootKeyHash = null; + } +} diff --git a/TestCode.cs b/WPinternals/TestCode.cs similarity index 97% rename from TestCode.cs rename to WPinternals/TestCode.cs index ca19b23..44fd6b4 100644 --- a/TestCode.cs +++ b/WPinternals/TestCode.cs @@ -1,295 +1,295 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace WPinternals -{ - internal static class TestCode - { - internal static async Task Test(System.Threading.SynchronizationContext UIContext) - { - // To avoid warnings when there is no code here. - await Task.Run(() => { }); - - // PhoneNotifierViewModel Notifier = new PhoneNotifierViewModel(); - // Notifier.Start(); - // await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); - // MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; - } - - internal static async Task RecoverBadGPT(string GPTPath, string LoadersPath) - { - byte[] GPT = File.ReadAllBytes(GPTPath); - - PhoneNotifierViewModel PhoneNotifier = new(); - PhoneNotifier.Start(); - await SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Qualcomm_Download); - - byte[] RootKeyHash = null; - if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) - { - QualcommDownload Download2 = new((QualcommSerial)PhoneNotifier.CurrentModel); - RootKeyHash = Download2.GetRKH(); - } - - List PossibleLoaders = null; - if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) - { - try - { - PossibleLoaders = QualcommLoaders.GetPossibleLoadersForRootKeyHash(LoadersPath, RootKeyHash); - if (PossibleLoaders.Count == 0) - { - throw new Exception("Error: No matching loaders found for RootKeyHash."); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - throw new Exception("Error: Unexpected error during scanning for loaders."); - } - } - - QualcommSerial Serial = (QualcommSerial)PhoneNotifier.CurrentModel; - QualcommDownload Download = new(Serial); - if (Download.IsAlive()) - { - int Attempt = 1; - bool Result = false; - foreach (QualcommPartition Loader in PossibleLoaders) - { - LogFile.Log("Attempt " + Attempt.ToString(), LogType.ConsoleOnly); - - try - { - Download.SendToPhoneMemory(0x2A000000, Loader.Binary); - Download.StartBootloader(0x2A000000); - Result = true; - LogFile.Log("Loader sent successfully", LogType.ConsoleOnly); - } - catch { } - - if (Result) - { - break; - } - - Attempt++; - } - Serial.Close(); - - if (!Result) - { - LogFile.Log("Loader failed", LogType.ConsoleOnly); - } - } - else - { - LogFile.Log("Failed to communicate to Qualcomm Emergency Download mode", LogType.ConsoleOnly); - throw new BadConnectionException(); - } - - if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Flash) - { - await PhoneNotifier.WaitForArrival(); - } - - if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Flash) - { - throw new WPinternalsException("Phone failed to switch to emergency flash mode."); - } - - // Flash bootloader - QualcommSerial Serial2 = (QualcommSerial)PhoneNotifier.CurrentModel; - Serial2.EncodeCommands = false; - - QualcommFlasher Flasher = new(Serial2); - - Flasher.Hello(); - Flasher.SetSecurityMode(0); - Flasher.OpenPartition(0x21); - - LogFile.Log("Partition opened.", LogType.ConsoleOnly); - - LogFile.Log("Flash GPT at 0x" + ((UInt32)0x200).ToString("X8"), LogType.ConsoleOnly); - Flasher.Flash(0x200, GPT, 0, 0x41FF); // Bad bounds-check in the flash-loader prohibits to write the last byte. - - Flasher.ClosePartition(); - - LogFile.Log("Partition closed. Flashing ready. Rebooting."); - - Flasher.Reboot(); - - Flasher.CloseSerial(); - } - - internal static async Task RewriteGPT(string GPTPath) - { - PhoneNotifierViewModel Notifier = new(); - Notifier.Start(); - await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); - MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; - - LogFile.Log("Writing GPT to the device.", LogType.ConsoleOnly); - MassStorage.WriteSectors(1, GPTPath); - } - - internal static async Task RewriteMBRGPT() - { - FFU FFU = new(@"E:\Device Backups\Alpha\9200_1230.0025.9200.9825\RX100_9825.ffu"); - const string GPTPath = @"E:\Device Backups\Alpha\9200_1230.0025.9200.9825\CorrectGPT.bin"; - - PhoneNotifierViewModel Notifier = new(); - Notifier.Start(); - await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); - MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; - - byte[] MBR = FFU.GetSectors(0, 1); - LogFile.Log("Writing MBR to the device.", LogType.ConsoleOnly); - MassStorage.WriteSectors(0, MBR); - - LogFile.Log("Writing GPT to the device.", LogType.ConsoleOnly); - MassStorage.WriteSectors(1, GPTPath); - } - - internal static async Task RewriteParts(string PartPath) - { - PhoneNotifierViewModel Notifier = new(); - Notifier.Start(); - await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); - MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; - - foreach (var part in Directory.EnumerateFiles(PartPath)) - { - var partname = part.Split('\\').Last().Replace(".img", ""); - try - { - LogFile.Log($"Writing {partname} to the device.", LogType.ConsoleOnly); - - LogFile.Log("", LogType.ConsoleOnly); - - MassStorage.RestorePartition(part, partname, (v, t) => LogFile.Log("Progress: " + v + "%", LogType.ConsoleOnly)); - LogFile.Log("", LogType.ConsoleOnly); - } - catch - { - LogFile.Log("", LogType.ConsoleOnly); - LogFile.Log($"Failed writing {partname} to the device.", LogType.ConsoleOnly); - } - } - } - - internal static void PatchImg(string dump) - { - using var fil = File.Open(dump, FileMode.Open); - byte[] gptbuffer = new byte[0x4200]; - - fil.Seek(0x200, SeekOrigin.Begin); - fil.Read(gptbuffer, 0, 0x4200); - - uint BackupLBA = ByteOperations.ReadUInt32(gptbuffer, 0x20); - uint LastUsableLBA = ByteOperations.ReadUInt32(gptbuffer, 0x30); - - LogFile.Log("Previous BackupLBA: " + BackupLBA, LogType.ConsoleOnly); - LogFile.Log("Previous LastUsableLBA: " + LastUsableLBA, LogType.ConsoleOnly); - - const uint NewBackupLBA = 62078975u; - const uint NewLastUsableLBA = 62078942u; - - ByteOperations.WriteUInt32(gptbuffer, 0x20, NewBackupLBA); - ByteOperations.WriteUInt32(gptbuffer, 0x30, NewLastUsableLBA); - - uint HeaderSize = ByteOperations.ReadUInt32(gptbuffer, 0x0C); - uint PrevCRC = ByteOperations.ReadUInt32(gptbuffer, 0x10); - - LogFile.Log("Previous CRC: " + PrevCRC, LogType.ConsoleOnly); - - ByteOperations.WriteUInt32(gptbuffer, 0x10, 0); - uint NewCRC = ByteOperations.CRC32(gptbuffer, 0, HeaderSize); - - LogFile.Log("New CRC: " + NewCRC, LogType.ConsoleOnly); - - ByteOperations.WriteUInt32(gptbuffer, 0x10, NewCRC); - - LogFile.Log("Writing", LogType.ConsoleOnly); - - fil.Seek(0x200, SeekOrigin.Begin); - fil.Write(gptbuffer, 0, 0x4200); - - LogFile.Log("Done!", LogType.ConsoleOnly); - } - - internal static async Task TestProgrammer(System.Threading.SynchronizationContext UIContext, string ProgrammerPath) - { - LogFile.BeginAction("TestProgrammer"); - try - { - LogFile.Log("Starting Firehose Test", LogType.FileAndConsole); - - PhoneNotifierViewModel Notifier = new(); - UIContext.Send(s => Notifier.Start(), null); - if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) - { - LogFile.Log("Phone found in emergency mode", LogType.FileAndConsole); - } - else - { - LogFile.Log("Phone needs to be switched to emergency mode.", LogType.FileAndConsole); - await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - PhoneInfo Info = ((NokiaFlashModel)Notifier.CurrentModel).ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Qualcomm_Download); - if (Notifier.CurrentInterface != PhoneInterfaces.Qualcomm_Download) - { - throw new WPinternalsException("Switching mode failed.", "Could not switch the phone to Qualcomm Emergency 9008."); - } - - LogFile.Log("Phone is in emergency mode.", LogType.FileAndConsole); - } - - // Send and start programmer - QualcommSerial Serial = (QualcommSerial)Notifier.CurrentModel; - QualcommSahara Sahara = new(Serial); - - if (await Sahara.Reset(ProgrammerPath)) - { - LogFile.Log("Emergency programmer test succeeded", LogType.FileAndConsole); - } - else - { - LogFile.Log("Emergency programmer test failed", LogType.FileAndConsole); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("TestProgrammer"); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace WPinternals +{ + internal static class TestCode + { + internal static async Task Test(System.Threading.SynchronizationContext UIContext) + { + // To avoid warnings when there is no code here. + await Task.Run(() => { }); + + // PhoneNotifierViewModel Notifier = new PhoneNotifierViewModel(); + // Notifier.Start(); + // await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); + // MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; + } + + internal static async Task RecoverBadGPT(string GPTPath, string LoadersPath) + { + byte[] GPT = File.ReadAllBytes(GPTPath); + + PhoneNotifierViewModel PhoneNotifier = new(); + PhoneNotifier.Start(); + await SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Qualcomm_Download); + + byte[] RootKeyHash = null; + if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) + { + QualcommDownload Download2 = new((QualcommSerial)PhoneNotifier.CurrentModel); + RootKeyHash = Download2.GetRKH(); + } + + List PossibleLoaders = null; + if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) + { + try + { + PossibleLoaders = QualcommLoaders.GetPossibleLoadersForRootKeyHash(LoadersPath, RootKeyHash); + if (PossibleLoaders.Count == 0) + { + throw new Exception("Error: No matching loaders found for RootKeyHash."); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + throw new Exception("Error: Unexpected error during scanning for loaders."); + } + } + + QualcommSerial Serial = (QualcommSerial)PhoneNotifier.CurrentModel; + QualcommDownload Download = new(Serial); + if (Download.IsAlive()) + { + int Attempt = 1; + bool Result = false; + foreach (QualcommPartition Loader in PossibleLoaders) + { + LogFile.Log("Attempt " + Attempt.ToString(), LogType.ConsoleOnly); + + try + { + Download.SendToPhoneMemory(0x2A000000, Loader.Binary); + Download.StartBootloader(0x2A000000); + Result = true; + LogFile.Log("Loader sent successfully", LogType.ConsoleOnly); + } + catch { } + + if (Result) + { + break; + } + + Attempt++; + } + Serial.Close(); + + if (!Result) + { + LogFile.Log("Loader failed", LogType.ConsoleOnly); + } + } + else + { + LogFile.Log("Failed to communicate to Qualcomm Emergency Download mode", LogType.ConsoleOnly); + throw new BadConnectionException(); + } + + if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Flash) + { + await PhoneNotifier.WaitForArrival(); + } + + if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Flash) + { + throw new WPinternalsException("Phone failed to switch to emergency flash mode."); + } + + // Flash bootloader + QualcommSerial Serial2 = (QualcommSerial)PhoneNotifier.CurrentModel; + Serial2.EncodeCommands = false; + + QualcommFlasher Flasher = new(Serial2); + + Flasher.Hello(); + Flasher.SetSecurityMode(0); + Flasher.OpenPartition(0x21); + + LogFile.Log("Partition opened.", LogType.ConsoleOnly); + + LogFile.Log("Flash GPT at 0x" + ((UInt32)0x200).ToString("X8"), LogType.ConsoleOnly); + Flasher.Flash(0x200, GPT, 0, 0x41FF); // Bad bounds-check in the flash-loader prohibits to write the last byte. + + Flasher.ClosePartition(); + + LogFile.Log("Partition closed. Flashing ready. Rebooting."); + + Flasher.Reboot(); + + Flasher.CloseSerial(); + } + + internal static async Task RewriteGPT(string GPTPath) + { + PhoneNotifierViewModel Notifier = new(); + Notifier.Start(); + await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); + MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; + + LogFile.Log("Writing GPT to the device.", LogType.ConsoleOnly); + MassStorage.WriteSectors(1, GPTPath); + } + + internal static async Task RewriteMBRGPT() + { + FFU FFU = new(@"E:\Device Backups\Alpha\9200_1230.0025.9200.9825\RX100_9825.ffu"); + const string GPTPath = @"E:\Device Backups\Alpha\9200_1230.0025.9200.9825\CorrectGPT.bin"; + + PhoneNotifierViewModel Notifier = new(); + Notifier.Start(); + await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); + MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; + + byte[] MBR = FFU.GetSectors(0, 1); + LogFile.Log("Writing MBR to the device.", LogType.ConsoleOnly); + MassStorage.WriteSectors(0, MBR); + + LogFile.Log("Writing GPT to the device.", LogType.ConsoleOnly); + MassStorage.WriteSectors(1, GPTPath); + } + + internal static async Task RewriteParts(string PartPath) + { + PhoneNotifierViewModel Notifier = new(); + Notifier.Start(); + await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); + MassStorage MassStorage = (MassStorage)Notifier.CurrentModel; + + foreach (var part in Directory.EnumerateFiles(PartPath)) + { + var partname = part.Split('\\').Last().Replace(".img", ""); + try + { + LogFile.Log($"Writing {partname} to the device.", LogType.ConsoleOnly); + + LogFile.Log("", LogType.ConsoleOnly); + + MassStorage.RestorePartition(part, partname, (v, t) => LogFile.Log("Progress: " + v + "%", LogType.ConsoleOnly)); + LogFile.Log("", LogType.ConsoleOnly); + } + catch + { + LogFile.Log("", LogType.ConsoleOnly); + LogFile.Log($"Failed writing {partname} to the device.", LogType.ConsoleOnly); + } + } + } + + internal static void PatchImg(string dump) + { + using var fil = File.Open(dump, FileMode.Open); + byte[] gptbuffer = new byte[0x4200]; + + fil.Seek(0x200, SeekOrigin.Begin); + fil.Read(gptbuffer, 0, 0x4200); + + uint BackupLBA = ByteOperations.ReadUInt32(gptbuffer, 0x20); + uint LastUsableLBA = ByteOperations.ReadUInt32(gptbuffer, 0x30); + + LogFile.Log("Previous BackupLBA: " + BackupLBA, LogType.ConsoleOnly); + LogFile.Log("Previous LastUsableLBA: " + LastUsableLBA, LogType.ConsoleOnly); + + const uint NewBackupLBA = 62078975u; + const uint NewLastUsableLBA = 62078942u; + + ByteOperations.WriteUInt32(gptbuffer, 0x20, NewBackupLBA); + ByteOperations.WriteUInt32(gptbuffer, 0x30, NewLastUsableLBA); + + uint HeaderSize = ByteOperations.ReadUInt32(gptbuffer, 0x0C); + uint PrevCRC = ByteOperations.ReadUInt32(gptbuffer, 0x10); + + LogFile.Log("Previous CRC: " + PrevCRC, LogType.ConsoleOnly); + + ByteOperations.WriteUInt32(gptbuffer, 0x10, 0); + uint NewCRC = ByteOperations.CRC32(gptbuffer, 0, HeaderSize); + + LogFile.Log("New CRC: " + NewCRC, LogType.ConsoleOnly); + + ByteOperations.WriteUInt32(gptbuffer, 0x10, NewCRC); + + LogFile.Log("Writing", LogType.ConsoleOnly); + + fil.Seek(0x200, SeekOrigin.Begin); + fil.Write(gptbuffer, 0, 0x4200); + + LogFile.Log("Done!", LogType.ConsoleOnly); + } + + internal static async Task TestProgrammer(System.Threading.SynchronizationContext UIContext, string ProgrammerPath) + { + LogFile.BeginAction("TestProgrammer"); + try + { + LogFile.Log("Starting Firehose Test", LogType.FileAndConsole); + + PhoneNotifierViewModel Notifier = new(); + UIContext.Send(s => Notifier.Start(), null); + if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) + { + LogFile.Log("Phone found in emergency mode", LogType.FileAndConsole); + } + else + { + LogFile.Log("Phone needs to be switched to emergency mode.", LogType.FileAndConsole); + await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + PhoneInfo Info = ((NokiaFlashModel)Notifier.CurrentModel).ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Qualcomm_Download); + if (Notifier.CurrentInterface != PhoneInterfaces.Qualcomm_Download) + { + throw new WPinternalsException("Switching mode failed.", "Could not switch the phone to Qualcomm Emergency 9008."); + } + + LogFile.Log("Phone is in emergency mode.", LogType.FileAndConsole); + } + + // Send and start programmer + QualcommSerial Serial = (QualcommSerial)Notifier.CurrentModel; + QualcommSahara Sahara = new(Serial); + + if (await Sahara.Reset(ProgrammerPath)) + { + LogFile.Log("Emergency programmer test succeeded", LogType.FileAndConsole); + } + else + { + LogFile.Log("Emergency programmer test failed", LogType.FileAndConsole); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("TestProgrammer"); + } + } + } +} diff --git a/ViewModels/AboutViewModel.cs b/WPinternals/ViewModels/AboutViewModel.cs similarity index 97% rename from ViewModels/AboutViewModel.cs rename to WPinternals/ViewModels/AboutViewModel.cs index 0d50faa..fdfedb6 100644 --- a/ViewModels/AboutViewModel.cs +++ b/WPinternals/ViewModels/AboutViewModel.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -namespace WPinternals -{ - internal class AboutViewModel : ContextViewModel - { - internal AboutViewModel() : base() { } - - public int MajorVersion - { - get - { - return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Major; - } - } - - public int MinorVersion - { - get - { - return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Minor; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace WPinternals +{ + internal class AboutViewModel : ContextViewModel + { + internal AboutViewModel() : base() { } + + public int MajorVersion + { + get + { + return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Major; + } + } + + public int MinorVersion + { + get + { + return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Minor; + } + } + } +} diff --git a/ViewModels/BackupTargetSelectionViewModel.cs b/WPinternals/ViewModels/BackupTargetSelectionViewModel.cs similarity index 97% rename from ViewModels/BackupTargetSelectionViewModel.cs rename to WPinternals/ViewModels/BackupTargetSelectionViewModel.cs index 8840e32..fe65957 100644 --- a/ViewModels/BackupTargetSelectionViewModel.cs +++ b/WPinternals/ViewModels/BackupTargetSelectionViewModel.cs @@ -1,237 +1,237 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - internal class BackupTargetSelectionViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - private readonly Action BackupCallback; - private readonly Action BackupArchiveCallback; - private readonly Action BackupArchiveProvisioningCallback; - internal Action SwitchToUnlockBoot; - - internal BackupTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action BackupArchiveCallback, Action BackupCallback, Action BackupArchiveProvisioningCallback) - : base() - { - this.PhoneNotifier = PhoneNotifier; - this.BackupCallback = BackupCallback; - this.BackupArchiveCallback = BackupArchiveCallback; - this.BackupArchiveProvisioningCallback = BackupArchiveProvisioningCallback; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - - this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - this.PhoneNotifier.DeviceRemoved += DeviceRemoved; - - new Thread(() => EvaluateViewState()).Start(); - } - - private string _ArchivePath; - public string ArchivePath - { - get - { - return _ArchivePath; - } - set - { - if (value != _ArchivePath) - { - _ArchivePath = value; - OnPropertyChanged(nameof(ArchivePath)); - } - } - } - - private string _EFIESPPath; - public string EFIESPPath - { - get - { - return _EFIESPPath; - } - set - { - if (value != _EFIESPPath) - { - _EFIESPPath = value; - OnPropertyChanged(nameof(EFIESPPath)); - } - } - } - - private string _MainOSPath; - public string MainOSPath - { - get - { - return _MainOSPath; - } - set - { - if (value != _MainOSPath) - { - _MainOSPath = value; - OnPropertyChanged(nameof(MainOSPath)); - } - } - } - - private string _DataPath; - public string DataPath - { - get - { - return _DataPath; - } - set - { - if (value != _DataPath) - { - _DataPath = value; - OnPropertyChanged(nameof(DataPath)); - } - } - } - - private string _ArchiveProvisioningPath; - public string ArchiveProvisioningPath - { - get - { - return _ArchiveProvisioningPath; - } - set - { - if (value != _ArchiveProvisioningPath) - { - _ArchiveProvisioningPath = value; - OnPropertyChanged(nameof(ArchiveProvisioningPath)); - } - } - } - - private bool _IsPhoneDisconnected; - public bool IsPhoneDisconnected - { - get - { - return _IsPhoneDisconnected; - } - set - { - if (value != _IsPhoneDisconnected) - { - _IsPhoneDisconnected = value; - OnPropertyChanged(nameof(IsPhoneDisconnected)); - } - } - } - - private bool _IsPhoneInMassStorage; - public bool IsPhoneInMassStorage - { - get - { - return _IsPhoneInMassStorage; - } - set - { - if (value != _IsPhoneInMassStorage) - { - _IsPhoneInMassStorage = value; - OnPropertyChanged(nameof(IsPhoneInMassStorage)); - } - } - } - - private bool _IsPhoneInOtherMode; - public bool IsPhoneInOtherMode - { - get - { - return _IsPhoneInOtherMode; - } - set - { - if (value != _IsPhoneInOtherMode) - { - _IsPhoneInOtherMode = value; - OnPropertyChanged(nameof(IsPhoneInOtherMode)); - } - } - } - - private DelegateCommand _BackupArchiveCommand; - public DelegateCommand BackupArchiveCommand - { - get - { - return _BackupArchiveCommand ??= new DelegateCommand(() => BackupArchiveCallback(ArchivePath), () => (ArchivePath != null) && (PhoneNotifier.CurrentInterface != null)); - } - } - - private DelegateCommand _BackupCommand; - public DelegateCommand BackupCommand - { - get - { - return _BackupCommand ??= new DelegateCommand(() => BackupCallback(EFIESPPath, MainOSPath, DataPath), () => ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null)) && (PhoneNotifier.CurrentInterface != null)); - } - } - - private DelegateCommand _BackupArchiveProvisioningCommand; - public DelegateCommand BackupArchiveProvisioningCommand - { - get - { - return _BackupArchiveProvisioningCommand ??= new DelegateCommand(() => BackupArchiveProvisioningCallback(ArchiveProvisioningPath), () => (ArchiveProvisioningPath != null) && (PhoneNotifier.CurrentInterface != null)); - } - } - - ~BackupTargetSelectionViewModel() - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - new Thread(() => EvaluateViewState()).Start(); - } - - private void DeviceRemoved() - { - new Thread(() => EvaluateViewState()).Start(); - } - - internal override void EvaluateViewState() - { - IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; - IsPhoneInMassStorage = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_MassStorage; - IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInMassStorage; - BackupCommand.RaiseCanExecuteChanged(); - BackupArchiveCommand.RaiseCanExecuteChanged(); - BackupArchiveProvisioningCommand.RaiseCanExecuteChanged(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + internal class BackupTargetSelectionViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + private readonly Action BackupCallback; + private readonly Action BackupArchiveCallback; + private readonly Action BackupArchiveProvisioningCallback; + internal Action SwitchToUnlockBoot; + + internal BackupTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action BackupArchiveCallback, Action BackupCallback, Action BackupArchiveProvisioningCallback) + : base() + { + this.PhoneNotifier = PhoneNotifier; + this.BackupCallback = BackupCallback; + this.BackupArchiveCallback = BackupArchiveCallback; + this.BackupArchiveProvisioningCallback = BackupArchiveProvisioningCallback; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + + this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + this.PhoneNotifier.DeviceRemoved += DeviceRemoved; + + new Thread(() => EvaluateViewState()).Start(); + } + + private string _ArchivePath; + public string ArchivePath + { + get + { + return _ArchivePath; + } + set + { + if (value != _ArchivePath) + { + _ArchivePath = value; + OnPropertyChanged(nameof(ArchivePath)); + } + } + } + + private string _EFIESPPath; + public string EFIESPPath + { + get + { + return _EFIESPPath; + } + set + { + if (value != _EFIESPPath) + { + _EFIESPPath = value; + OnPropertyChanged(nameof(EFIESPPath)); + } + } + } + + private string _MainOSPath; + public string MainOSPath + { + get + { + return _MainOSPath; + } + set + { + if (value != _MainOSPath) + { + _MainOSPath = value; + OnPropertyChanged(nameof(MainOSPath)); + } + } + } + + private string _DataPath; + public string DataPath + { + get + { + return _DataPath; + } + set + { + if (value != _DataPath) + { + _DataPath = value; + OnPropertyChanged(nameof(DataPath)); + } + } + } + + private string _ArchiveProvisioningPath; + public string ArchiveProvisioningPath + { + get + { + return _ArchiveProvisioningPath; + } + set + { + if (value != _ArchiveProvisioningPath) + { + _ArchiveProvisioningPath = value; + OnPropertyChanged(nameof(ArchiveProvisioningPath)); + } + } + } + + private bool _IsPhoneDisconnected; + public bool IsPhoneDisconnected + { + get + { + return _IsPhoneDisconnected; + } + set + { + if (value != _IsPhoneDisconnected) + { + _IsPhoneDisconnected = value; + OnPropertyChanged(nameof(IsPhoneDisconnected)); + } + } + } + + private bool _IsPhoneInMassStorage; + public bool IsPhoneInMassStorage + { + get + { + return _IsPhoneInMassStorage; + } + set + { + if (value != _IsPhoneInMassStorage) + { + _IsPhoneInMassStorage = value; + OnPropertyChanged(nameof(IsPhoneInMassStorage)); + } + } + } + + private bool _IsPhoneInOtherMode; + public bool IsPhoneInOtherMode + { + get + { + return _IsPhoneInOtherMode; + } + set + { + if (value != _IsPhoneInOtherMode) + { + _IsPhoneInOtherMode = value; + OnPropertyChanged(nameof(IsPhoneInOtherMode)); + } + } + } + + private DelegateCommand _BackupArchiveCommand; + public DelegateCommand BackupArchiveCommand + { + get + { + return _BackupArchiveCommand ??= new DelegateCommand(() => BackupArchiveCallback(ArchivePath), () => (ArchivePath != null) && (PhoneNotifier.CurrentInterface != null)); + } + } + + private DelegateCommand _BackupCommand; + public DelegateCommand BackupCommand + { + get + { + return _BackupCommand ??= new DelegateCommand(() => BackupCallback(EFIESPPath, MainOSPath, DataPath), () => ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null)) && (PhoneNotifier.CurrentInterface != null)); + } + } + + private DelegateCommand _BackupArchiveProvisioningCommand; + public DelegateCommand BackupArchiveProvisioningCommand + { + get + { + return _BackupArchiveProvisioningCommand ??= new DelegateCommand(() => BackupArchiveProvisioningCallback(ArchiveProvisioningPath), () => (ArchiveProvisioningPath != null) && (PhoneNotifier.CurrentInterface != null)); + } + } + + ~BackupTargetSelectionViewModel() + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + new Thread(() => EvaluateViewState()).Start(); + } + + private void DeviceRemoved() + { + new Thread(() => EvaluateViewState()).Start(); + } + + internal override void EvaluateViewState() + { + IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; + IsPhoneInMassStorage = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_MassStorage; + IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInMassStorage; + BackupCommand.RaiseCanExecuteChanged(); + BackupArchiveCommand.RaiseCanExecuteChanged(); + BackupArchiveProvisioningCommand.RaiseCanExecuteChanged(); + } + } +} diff --git a/ViewModels/BackupViewModel.cs b/WPinternals/ViewModels/BackupViewModel.cs similarity index 97% rename from ViewModels/BackupViewModel.cs rename to WPinternals/ViewModels/BackupViewModel.cs index 117cdbc..c747840 100644 --- a/ViewModels/BackupViewModel.cs +++ b/WPinternals/ViewModels/BackupViewModel.cs @@ -1,490 +1,490 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Threading; - -namespace WPinternals -{ - internal class BackupViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - private readonly Action Callback; - private readonly Action SwitchToUnlockBoot; - - internal BackupViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action Callback) - : base() - { - IsFlashModeOperation = true; - - this.PhoneNotifier = PhoneNotifier; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.Callback = Callback; - } - - internal override void EvaluateViewState() - { - if (!IsActive) - { - return; - } - - if (SubContextViewModel == null) - { - ActivateSubContext(new BackupTargetSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, DoBackupArchive, DoBackup, DoBackupArchiveProvisioning)); - IsSwitchingInterface = false; - } - - if (SubContextViewModel is BackupTargetSelectionViewModel) - { - ((BackupTargetSelectionViewModel)SubContextViewModel).EvaluateViewState(); - } - } - - internal async void DoBackup(string EFIESPPath, string MainOSPath, string DataPath) - { - try - { - IsSwitchingInterface = true; - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, - (msg, sub) => ActivateSubContext(new BusyViewModel(msg, sub))); - BackupTask(EFIESPPath, MainOSPath, DataPath); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal async void DoBackupArchive(string ArchivePath) - { - try - { - IsSwitchingInterface = true; - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, - (msg, sub) => ActivateSubContext(new BusyViewModel(msg, sub))); - BackupArchiveTask(ArchivePath); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal async void DoBackupArchiveProvisioning(string ArchiveProvisioningPath) - { - try - { - IsSwitchingInterface = true; - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, - (msg, sub) => ActivateSubContext(new BusyViewModel(msg, sub))); - BackupArchiveProvisioningTask(ArchiveProvisioningPath); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal void BackupTask(string EFIESPPath, string MainOSPath, string DataPath) - { - IsSwitchingInterface = false; - new Thread(() => - { - bool Result = true; - - ActivateSubContext(new BusyViewModel("Initializing backup...")); - - ulong TotalSizeSectors = 0; - int PartitionCount = 0; - - MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; - - Phone.OpenVolume(false); - byte[] GPTBuffer = Phone.ReadSectors(1, 33); - GPT GPT = new(GPTBuffer); - Partition Partition; - try - { - if (EFIESPPath != null) - { - Partition = GPT.Partitions.First(p => p.Name == "EFIESP"); - TotalSizeSectors += Partition.SizeInSectors; - PartitionCount++; - } - - if (MainOSPath != null) - { - Partition = GPT.Partitions.First(p => p.Name == "MainOS"); - TotalSizeSectors += Partition.SizeInSectors; - PartitionCount++; - } - - if (DataPath != null) - { - Partition = GPT.Partitions.First(p => p.Name == "Data"); - TotalSizeSectors += Partition.SizeInSectors; - PartitionCount++; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - BusyViewModel Busy = new("Create backup...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); - ProgressUpdater Updater = Busy.ProgressUpdater; - ActivateSubContext(Busy); - - int i = 0; - if (Result) - { - try - { - if (EFIESPPath != null) - { - i++; - Busy.Message = "Create backup of partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.BackupPartition("EFIESP", EFIESPPath, Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (MainOSPath != null) - { - i++; - Busy.Message = "Create backup of partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.BackupPartition("MainOS", MainOSPath, Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (DataPath != null) - { - i++; - Busy.Message = "Create backup of partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.BackupPartition("Data", DataPath, Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - Phone.CloseVolume(); - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to create backup!", Exit)); - return; - } - - ActivateSubContext(new MessageViewModel("Successfully created a backup!", Exit)); - }).Start(); - } - - internal void BackupArchiveTask(string ArchivePath) - { - IsSwitchingInterface = false; - new Thread(() => - { - bool Result = true; - - ActivateSubContext(new BusyViewModel("Initializing backup...")); - - ulong TotalSizeSectors = 0; - const int PartitionCount = 3; - - MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; - - try - { - Phone.OpenVolume(false); - byte[] GPTBuffer = Phone.ReadSectors(1, 33); - GPT GPT = new(GPTBuffer); - - Partition Partition; - - try - { - Partition = GPT.Partitions.First(p => p.Name == "EFIESP"); - TotalSizeSectors += Partition.SizeInSectors; - - Partition = GPT.Partitions.First(p => p.Name == "MainOS"); - TotalSizeSectors += Partition.SizeInSectors; - - Partition = GPT.Partitions.First(p => p.Name == "Data"); - TotalSizeSectors += Partition.SizeInSectors; - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - BusyViewModel Busy = new("Create backup...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); - ProgressUpdater Updater = Busy.ProgressUpdater; - ActivateSubContext(Busy); - ZipArchiveEntry Entry; - Stream EntryStream = null; - - using FileStream FileStream = new(ArchivePath, FileMode.Create); - using ZipArchive Archive = new(FileStream, ZipArchiveMode.Create); - int i = 0; - - if (Result) - { - try - { - Entry = Archive.CreateEntry("EFIESP.bin", CompressionLevel.Optimal); - EntryStream = Entry.Open(); - i++; - Busy.Message = "Create backup of partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.BackupPartition("EFIESP", EntryStream, Updater); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - finally - { - EntryStream?.Close(); - EntryStream = null; - } - } - - if (Result) - { - try - { - Entry = Archive.CreateEntry("MainOS.bin", CompressionLevel.Optimal); - EntryStream = Entry.Open(); - i++; - Busy.Message = "Create backup of partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.BackupPartition("MainOS", EntryStream, Updater); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - finally - { - EntryStream?.Close(); - EntryStream = null; - } - } - - if (Result) - { - try - { - Entry = Archive.CreateEntry("Data.bin", CompressionLevel.Optimal); - EntryStream = Entry.Open(); - i++; - Busy.Message = "Create backup of partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.BackupPartition("Data", EntryStream, Updater); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - finally - { - EntryStream?.Close(); - EntryStream = null; - } - } - } - catch { } - finally - { - Phone.CloseVolume(); - } - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to create backup!", Exit)); - return; - } - - ActivateSubContext(new MessageViewModel("Successfully created a backup!", Exit)); - }).Start(); - } - - private readonly static string[] ProvisioningPartitions = new string[] - { - "DPP", - "MODEM_FSG", - "MODEM_FS1", - "MODEM_FS2", - "MODEM_FSC", - "DDR", - "SEC", - "APDP", - "MSADP", - "DPO", - "SSD", - "DBI", - "UEFI_BS_NV", - "UEFI_NV", - "UEFI_RT_NV", - "UEFI_RT_NV_RPMB", - "BOOTMODE", - "LIMITS" - }; - - internal void BackupArchiveProvisioningTask(string ArchiveProvisioningPath) - { - IsSwitchingInterface = false; - new Thread(() => - { - bool Result = true; - - ActivateSubContext(new BusyViewModel("Initializing backup...")); - - ulong TotalSizeSectors = 0; - int PartitionCount = 0; - - MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; - - try - { - Phone.OpenVolume(false); - byte[] GPTBuffer = Phone.ReadSectors(1, 33); - GPT GPT = new(GPTBuffer); - - Partition Partition; - - try - { - foreach (string PartitionName in ProvisioningPartitions) - { - if (GPT.Partitions.Any(p => p.Name == PartitionName)) - { - Partition = GPT.Partitions.First(p => p.Name == PartitionName); - if (PartitionName == "UEFI_BS_NV" && GPT.Partitions.Any(p => p.Name == "BACKUP_BS_NV")) - { - Partition = GPT.Partitions.First(p => p.Name == "BACKUP_BS_NV"); - } - - TotalSizeSectors += Partition.SizeInSectors; - PartitionCount++; - } - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - BusyViewModel Busy = new("Create backup...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); - ProgressUpdater Updater = Busy.ProgressUpdater; - ActivateSubContext(Busy); - ZipArchiveEntry Entry; - Stream EntryStream = null; - - using FileStream FileStream = new(ArchiveProvisioningPath, FileMode.Create); - using ZipArchive Archive = new(FileStream, ZipArchiveMode.Create); - int i = 0; - - foreach (string PartitionName in ProvisioningPartitions) - { - if (GPT.Partitions.Any(p => p.Name == PartitionName) && Result) - { - try - { - Entry = Archive.CreateEntry(PartitionName + ".bin", CompressionLevel.Optimal); - EntryStream = Entry.Open(); - i++; - Busy.Message = "Create backup of partition " + PartitionName + " (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - if (PartitionName == "UEFI_BS_NV" && GPT.Partitions.Any(p => p.Name == "BACKUP_BS_NV")) - { - Phone.BackupPartition("BACKUP_BS_NV", EntryStream, Updater); - } - else - { - Phone.BackupPartition(PartitionName, EntryStream, Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - finally - { - EntryStream?.Close(); - EntryStream = null; - } - } - } - } - catch { } - finally - { - Phone.CloseVolume(); - } - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to create backup!", Exit)); - return; - } - - ActivateSubContext(new MessageViewModel("Successfully created a backup!", Exit)); - }).Start(); - } - - private void Exit() - { - IsSwitchingInterface = false; - ActivateSubContext(null); - Callback(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading; + +namespace WPinternals +{ + internal class BackupViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + private readonly Action Callback; + private readonly Action SwitchToUnlockBoot; + + internal BackupViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action Callback) + : base() + { + IsFlashModeOperation = true; + + this.PhoneNotifier = PhoneNotifier; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.Callback = Callback; + } + + internal override void EvaluateViewState() + { + if (!IsActive) + { + return; + } + + if (SubContextViewModel == null) + { + ActivateSubContext(new BackupTargetSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, DoBackupArchive, DoBackup, DoBackupArchiveProvisioning)); + IsSwitchingInterface = false; + } + + if (SubContextViewModel is BackupTargetSelectionViewModel) + { + ((BackupTargetSelectionViewModel)SubContextViewModel).EvaluateViewState(); + } + } + + internal async void DoBackup(string EFIESPPath, string MainOSPath, string DataPath) + { + try + { + IsSwitchingInterface = true; + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, + (msg, sub) => ActivateSubContext(new BusyViewModel(msg, sub))); + BackupTask(EFIESPPath, MainOSPath, DataPath); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal async void DoBackupArchive(string ArchivePath) + { + try + { + IsSwitchingInterface = true; + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, + (msg, sub) => ActivateSubContext(new BusyViewModel(msg, sub))); + BackupArchiveTask(ArchivePath); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal async void DoBackupArchiveProvisioning(string ArchiveProvisioningPath) + { + try + { + IsSwitchingInterface = true; + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, + (msg, sub) => ActivateSubContext(new BusyViewModel(msg, sub))); + BackupArchiveProvisioningTask(ArchiveProvisioningPath); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal void BackupTask(string EFIESPPath, string MainOSPath, string DataPath) + { + IsSwitchingInterface = false; + new Thread(() => + { + bool Result = true; + + ActivateSubContext(new BusyViewModel("Initializing backup...")); + + ulong TotalSizeSectors = 0; + int PartitionCount = 0; + + MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; + + Phone.OpenVolume(false); + byte[] GPTBuffer = Phone.ReadSectors(1, 33); + GPT GPT = new(GPTBuffer); + Partition Partition; + try + { + if (EFIESPPath != null) + { + Partition = GPT.Partitions.First(p => p.Name == "EFIESP"); + TotalSizeSectors += Partition.SizeInSectors; + PartitionCount++; + } + + if (MainOSPath != null) + { + Partition = GPT.Partitions.First(p => p.Name == "MainOS"); + TotalSizeSectors += Partition.SizeInSectors; + PartitionCount++; + } + + if (DataPath != null) + { + Partition = GPT.Partitions.First(p => p.Name == "Data"); + TotalSizeSectors += Partition.SizeInSectors; + PartitionCount++; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + BusyViewModel Busy = new("Create backup...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); + ProgressUpdater Updater = Busy.ProgressUpdater; + ActivateSubContext(Busy); + + int i = 0; + if (Result) + { + try + { + if (EFIESPPath != null) + { + i++; + Busy.Message = "Create backup of partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.BackupPartition("EFIESP", EFIESPPath, Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (MainOSPath != null) + { + i++; + Busy.Message = "Create backup of partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.BackupPartition("MainOS", MainOSPath, Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (DataPath != null) + { + i++; + Busy.Message = "Create backup of partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.BackupPartition("Data", DataPath, Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + Phone.CloseVolume(); + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to create backup!", Exit)); + return; + } + + ActivateSubContext(new MessageViewModel("Successfully created a backup!", Exit)); + }).Start(); + } + + internal void BackupArchiveTask(string ArchivePath) + { + IsSwitchingInterface = false; + new Thread(() => + { + bool Result = true; + + ActivateSubContext(new BusyViewModel("Initializing backup...")); + + ulong TotalSizeSectors = 0; + const int PartitionCount = 3; + + MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; + + try + { + Phone.OpenVolume(false); + byte[] GPTBuffer = Phone.ReadSectors(1, 33); + GPT GPT = new(GPTBuffer); + + Partition Partition; + + try + { + Partition = GPT.Partitions.First(p => p.Name == "EFIESP"); + TotalSizeSectors += Partition.SizeInSectors; + + Partition = GPT.Partitions.First(p => p.Name == "MainOS"); + TotalSizeSectors += Partition.SizeInSectors; + + Partition = GPT.Partitions.First(p => p.Name == "Data"); + TotalSizeSectors += Partition.SizeInSectors; + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + BusyViewModel Busy = new("Create backup...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); + ProgressUpdater Updater = Busy.ProgressUpdater; + ActivateSubContext(Busy); + ZipArchiveEntry Entry; + Stream EntryStream = null; + + using FileStream FileStream = new(ArchivePath, FileMode.Create); + using ZipArchive Archive = new(FileStream, ZipArchiveMode.Create); + int i = 0; + + if (Result) + { + try + { + Entry = Archive.CreateEntry("EFIESP.bin", CompressionLevel.Optimal); + EntryStream = Entry.Open(); + i++; + Busy.Message = "Create backup of partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.BackupPartition("EFIESP", EntryStream, Updater); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + finally + { + EntryStream?.Close(); + EntryStream = null; + } + } + + if (Result) + { + try + { + Entry = Archive.CreateEntry("MainOS.bin", CompressionLevel.Optimal); + EntryStream = Entry.Open(); + i++; + Busy.Message = "Create backup of partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.BackupPartition("MainOS", EntryStream, Updater); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + finally + { + EntryStream?.Close(); + EntryStream = null; + } + } + + if (Result) + { + try + { + Entry = Archive.CreateEntry("Data.bin", CompressionLevel.Optimal); + EntryStream = Entry.Open(); + i++; + Busy.Message = "Create backup of partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.BackupPartition("Data", EntryStream, Updater); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + finally + { + EntryStream?.Close(); + EntryStream = null; + } + } + } + catch { } + finally + { + Phone.CloseVolume(); + } + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to create backup!", Exit)); + return; + } + + ActivateSubContext(new MessageViewModel("Successfully created a backup!", Exit)); + }).Start(); + } + + private readonly static string[] ProvisioningPartitions = new string[] + { + "DPP", + "MODEM_FSG", + "MODEM_FS1", + "MODEM_FS2", + "MODEM_FSC", + "DDR", + "SEC", + "APDP", + "MSADP", + "DPO", + "SSD", + "DBI", + "UEFI_BS_NV", + "UEFI_NV", + "UEFI_RT_NV", + "UEFI_RT_NV_RPMB", + "BOOTMODE", + "LIMITS" + }; + + internal void BackupArchiveProvisioningTask(string ArchiveProvisioningPath) + { + IsSwitchingInterface = false; + new Thread(() => + { + bool Result = true; + + ActivateSubContext(new BusyViewModel("Initializing backup...")); + + ulong TotalSizeSectors = 0; + int PartitionCount = 0; + + MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; + + try + { + Phone.OpenVolume(false); + byte[] GPTBuffer = Phone.ReadSectors(1, 33); + GPT GPT = new(GPTBuffer); + + Partition Partition; + + try + { + foreach (string PartitionName in ProvisioningPartitions) + { + if (GPT.Partitions.Any(p => p.Name == PartitionName)) + { + Partition = GPT.Partitions.First(p => p.Name == PartitionName); + if (PartitionName == "UEFI_BS_NV" && GPT.Partitions.Any(p => p.Name == "BACKUP_BS_NV")) + { + Partition = GPT.Partitions.First(p => p.Name == "BACKUP_BS_NV"); + } + + TotalSizeSectors += Partition.SizeInSectors; + PartitionCount++; + } + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + BusyViewModel Busy = new("Create backup...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); + ProgressUpdater Updater = Busy.ProgressUpdater; + ActivateSubContext(Busy); + ZipArchiveEntry Entry; + Stream EntryStream = null; + + using FileStream FileStream = new(ArchiveProvisioningPath, FileMode.Create); + using ZipArchive Archive = new(FileStream, ZipArchiveMode.Create); + int i = 0; + + foreach (string PartitionName in ProvisioningPartitions) + { + if (GPT.Partitions.Any(p => p.Name == PartitionName) && Result) + { + try + { + Entry = Archive.CreateEntry(PartitionName + ".bin", CompressionLevel.Optimal); + EntryStream = Entry.Open(); + i++; + Busy.Message = "Create backup of partition " + PartitionName + " (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + if (PartitionName == "UEFI_BS_NV" && GPT.Partitions.Any(p => p.Name == "BACKUP_BS_NV")) + { + Phone.BackupPartition("BACKUP_BS_NV", EntryStream, Updater); + } + else + { + Phone.BackupPartition(PartitionName, EntryStream, Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + finally + { + EntryStream?.Close(); + EntryStream = null; + } + } + } + } + catch { } + finally + { + Phone.CloseVolume(); + } + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to create backup!", Exit)); + return; + } + + ActivateSubContext(new MessageViewModel("Successfully created a backup!", Exit)); + }).Start(); + } + + private void Exit() + { + IsSwitchingInterface = false; + ActivateSubContext(null); + Callback(); + } + } +} diff --git a/ViewModels/BusyViewModel.cs b/WPinternals/ViewModels/BusyViewModel.cs similarity index 96% rename from ViewModels/BusyViewModel.cs rename to WPinternals/ViewModels/BusyViewModel.cs index 6b2af22..2bae9eb 100644 --- a/ViewModels/BusyViewModel.cs +++ b/WPinternals/ViewModels/BusyViewModel.cs @@ -1,217 +1,217 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - internal class BusyViewModel : ContextViewModel - { - private readonly ulong MaxProgressValue = 0; - internal ProgressUpdater ProgressUpdater = null; - - // UIContext can be passed to BusyViewModel, when it needs to update progress-controls and it is created on a worker-thread. - internal BusyViewModel(string Message, string SubMessage = null, ulong? MaxProgressValue = null, SynchronizationContext UIContext = null, bool ShowAnimation = true, bool ShowRebootHelp = false) - { - LogFile.Log(Message); - - this.UIContext = UIContext ?? SynchronizationContext.Current; - - this.Message = Message; - this.SubMessage = SubMessage; - this.ShowAnimation = ShowAnimation; - this.ShowRebootHelp = ShowRebootHelp; - if (MaxProgressValue != null) - { - ProgressPercentage = 0; - this.MaxProgressValue = (ulong)MaxProgressValue; - ProgressUpdater = new ProgressUpdater((ulong)MaxProgressValue, (p, t) => - { - if ((this.UIContext == null) || (this.UIContext == SynchronizationContext.Current)) - { - ProgressPercentage = p; - TimeRemaining = t; - } - else - { - this.UIContext.Post((s) => - { - ProgressPercentage = p; - TimeRemaining = t; - }, null); - } - }); - } - } - - internal void SetShowRebootHelp(bool Value) - { - ShowRebootHelp = Value; - } - - internal void SetProgress(ulong Value) - { - if (ProgressUpdater != null) - { - UIContext.Post((s) => ProgressUpdater.SetProgress(Value), null); - } - } - - private string _Message = null; - public string Message - { - get - { - return _Message; - } - set - { - _Message = value; - OnPropertyChanged(nameof(Message)); - } - } - - private string _SubMessage = null; - public string SubMessage - { - get - { - return _SubMessage; - } - set - { - _SubMessage = value; - OnPropertyChanged(nameof(SubMessage)); - } - } - - private int? _ProgressPercentage = null; - public int? ProgressPercentage - { - get - { - return _ProgressPercentage; - } - set - { - if (_ProgressPercentage != value) - { - _ProgressPercentage = value; - OnPropertyChanged(nameof(ProgressPercentage)); - OnPropertyChanged(nameof(ShowAnimation)); - UpdateProgressText(); - } - } - } - - private TimeSpan? _TimeRemaining = null; - public TimeSpan? TimeRemaining - { - get - { - return _TimeRemaining; - } - set - { - if (_TimeRemaining != value) - { - _TimeRemaining = value; - OnPropertyChanged(nameof(TimeRemaining)); - UpdateProgressText(); - } - } - } - - private void UpdateProgressText() - { - string NewText = null; - if (ProgressPercentage != null) - { - NewText = "Progress: " + ((int)ProgressPercentage).ToString() + "%"; - } - if (TimeRemaining != null) - { - if (NewText == null) - { - NewText = ""; - } - else - { - NewText += " - "; - } - - NewText += "Estimated time remaining: " + ((TimeSpan)TimeRemaining).ToString(@"h\:mm\:ss"); - } - ProgressText = NewText; - } - - private string _ProgressText = null; - public string ProgressText - { - get - { - return _ProgressText; - } - set - { - if (_ProgressText != value) - { - _ProgressText = value; - OnPropertyChanged(nameof(ProgressText)); - } - } - } - - private bool _ShowAnimation = true; - public bool ShowAnimation - { - get - { - return (_ProgressPercentage == null) && _ShowAnimation; - } - set - { - if (_ShowAnimation != value) - { - _ShowAnimation = value; - OnPropertyChanged(nameof(ShowAnimation)); - } - } - } - - private bool _ShowRebootHelp = false; - public bool ShowRebootHelp - { - get - { - return _ShowRebootHelp; - } - set - { - if (_ShowRebootHelp != value) - { - _ShowRebootHelp = value; - OnPropertyChanged(nameof(ShowRebootHelp)); - } - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + internal class BusyViewModel : ContextViewModel + { + private readonly ulong MaxProgressValue = 0; + internal ProgressUpdater ProgressUpdater = null; + + // UIContext can be passed to BusyViewModel, when it needs to update progress-controls and it is created on a worker-thread. + internal BusyViewModel(string Message, string SubMessage = null, ulong? MaxProgressValue = null, SynchronizationContext UIContext = null, bool ShowAnimation = true, bool ShowRebootHelp = false) + { + LogFile.Log(Message); + + this.UIContext = UIContext ?? SynchronizationContext.Current; + + this.Message = Message; + this.SubMessage = SubMessage; + this.ShowAnimation = ShowAnimation; + this.ShowRebootHelp = ShowRebootHelp; + if (MaxProgressValue != null) + { + ProgressPercentage = 0; + this.MaxProgressValue = (ulong)MaxProgressValue; + ProgressUpdater = new ProgressUpdater((ulong)MaxProgressValue, (p, t) => + { + if ((this.UIContext == null) || (this.UIContext == SynchronizationContext.Current)) + { + ProgressPercentage = p; + TimeRemaining = t; + } + else + { + this.UIContext.Post((s) => + { + ProgressPercentage = p; + TimeRemaining = t; + }, null); + } + }); + } + } + + internal void SetShowRebootHelp(bool Value) + { + ShowRebootHelp = Value; + } + + internal void SetProgress(ulong Value) + { + if (ProgressUpdater != null) + { + UIContext.Post((s) => ProgressUpdater.SetProgress(Value), null); + } + } + + private string _Message = null; + public string Message + { + get + { + return _Message; + } + set + { + _Message = value; + OnPropertyChanged(nameof(Message)); + } + } + + private string _SubMessage = null; + public string SubMessage + { + get + { + return _SubMessage; + } + set + { + _SubMessage = value; + OnPropertyChanged(nameof(SubMessage)); + } + } + + private int? _ProgressPercentage = null; + public int? ProgressPercentage + { + get + { + return _ProgressPercentage; + } + set + { + if (_ProgressPercentage != value) + { + _ProgressPercentage = value; + OnPropertyChanged(nameof(ProgressPercentage)); + OnPropertyChanged(nameof(ShowAnimation)); + UpdateProgressText(); + } + } + } + + private TimeSpan? _TimeRemaining = null; + public TimeSpan? TimeRemaining + { + get + { + return _TimeRemaining; + } + set + { + if (_TimeRemaining != value) + { + _TimeRemaining = value; + OnPropertyChanged(nameof(TimeRemaining)); + UpdateProgressText(); + } + } + } + + private void UpdateProgressText() + { + string NewText = null; + if (ProgressPercentage != null) + { + NewText = "Progress: " + ((int)ProgressPercentage).ToString() + "%"; + } + if (TimeRemaining != null) + { + if (NewText == null) + { + NewText = ""; + } + else + { + NewText += " - "; + } + + NewText += "Estimated time remaining: " + ((TimeSpan)TimeRemaining).ToString(@"h\:mm\:ss"); + } + ProgressText = NewText; + } + + private string _ProgressText = null; + public string ProgressText + { + get + { + return _ProgressText; + } + set + { + if (_ProgressText != value) + { + _ProgressText = value; + OnPropertyChanged(nameof(ProgressText)); + } + } + } + + private bool _ShowAnimation = true; + public bool ShowAnimation + { + get + { + return (_ProgressPercentage == null) && _ShowAnimation; + } + set + { + if (_ShowAnimation != value) + { + _ShowAnimation = value; + OnPropertyChanged(nameof(ShowAnimation)); + } + } + } + + private bool _ShowRebootHelp = false; + public bool ShowRebootHelp + { + get + { + return _ShowRebootHelp; + } + set + { + if (_ShowRebootHelp != value) + { + _ShowRebootHelp = value; + OnPropertyChanged(nameof(ShowRebootHelp)); + } + } + } + } +} diff --git a/ViewModels/ContextViewModel.cs b/WPinternals/ViewModels/ContextViewModel.cs similarity index 97% rename from ViewModels/ContextViewModel.cs rename to WPinternals/ViewModels/ContextViewModel.cs index 635cf65..493ee77 100644 --- a/ViewModels/ContextViewModel.cs +++ b/WPinternals/ViewModels/ContextViewModel.cs @@ -1,157 +1,157 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.ComponentModel; -using System.Threading; - -namespace WPinternals -{ - internal class ContextViewModel : INotifyPropertyChanged - { - protected SynchronizationContext UIContext; - - public bool IsSwitchingInterface = false; - public bool IsFlashModeOperation = false; - - public event PropertyChangedEventHandler PropertyChanged = delegate { }; - - protected void OnPropertyChanged(string propertyName) - { - if ((UIContext == null) && (SynchronizationContext.Current != null)) - { - UIContext = SynchronizationContext.Current; - } - - if (this.PropertyChanged != null) - { - if (SynchronizationContext.Current == UIContext) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } - else - { - UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); - } - } - } - - private ContextViewModel _SubContextViewModel; - public ContextViewModel SubContextViewModel - { - get - { - return _SubContextViewModel; - } - private set - { - if (_SubContextViewModel != null) - { - _SubContextViewModel.IsActive = false; - } - - _SubContextViewModel = value; - if (_SubContextViewModel != null) - { - _SubContextViewModel.IsActive = IsActive; - } - - OnPropertyChanged(nameof(SubContextViewModel)); - } - } - - internal ContextViewModel() - { - UIContext = SynchronizationContext.Current; - } - - internal ContextViewModel(MainViewModel Main) : this() - { - } - - internal ContextViewModel(MainViewModel Main, ContextViewModel SubContext) : this(Main) - { - SubContextViewModel = SubContext; - } - - internal bool IsActive { get; set; } = false; - - internal virtual void EvaluateViewState() - { - } - - internal void Activate() - { - IsActive = true; - EvaluateViewState(); - SubContextViewModel?.Activate(); - } - - internal void ActivateSubContext(ContextViewModel NewSubContext) - { - if (_SubContextViewModel != null) - { - _SubContextViewModel.IsActive = false; - } - - if (NewSubContext != null) - { - if (IsActive) - { - NewSubContext.Activate(); - } - else - { - NewSubContext.IsActive = false; - } - } - SubContextViewModel = NewSubContext; - } - - internal void SetWorkingStatus(string Message, string SubMessage, ulong? MaxProgressValue, bool ShowAnimation = true, WPinternalsStatus Status = WPinternalsStatus.Undefined) - { - ActivateSubContext(new BusyViewModel(Message, SubMessage, MaxProgressValue, UIContext: UIContext, ShowAnimation: ShowAnimation, ShowRebootHelp: Status == WPinternalsStatus.WaitingForManualReset)); - } - - internal void UpdateWorkingStatus(string Message, string SubMessage, ulong? CurrentProgressValue, WPinternalsStatus Status = WPinternalsStatus.Undefined) - { - if (SubContextViewModel is BusyViewModel Busy) - { - if (Message != null) - { - Busy.Message = Message; - Busy.SubMessage = SubMessage; - } - if ((CurrentProgressValue != null) && (Busy.ProgressUpdater != null)) - { - try - { - Busy.ProgressUpdater.SetProgress((ulong)CurrentProgressValue); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - } - Busy.SetShowRebootHelp(Status == WPinternalsStatus.WaitingForManualReset); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.ComponentModel; +using System.Threading; + +namespace WPinternals +{ + internal class ContextViewModel : INotifyPropertyChanged + { + protected SynchronizationContext UIContext; + + public bool IsSwitchingInterface = false; + public bool IsFlashModeOperation = false; + + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + + protected void OnPropertyChanged(string propertyName) + { + if ((UIContext == null) && (SynchronizationContext.Current != null)) + { + UIContext = SynchronizationContext.Current; + } + + if (this.PropertyChanged != null) + { + if (SynchronizationContext.Current == UIContext) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + else + { + UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); + } + } + } + + private ContextViewModel _SubContextViewModel; + public ContextViewModel SubContextViewModel + { + get + { + return _SubContextViewModel; + } + private set + { + if (_SubContextViewModel != null) + { + _SubContextViewModel.IsActive = false; + } + + _SubContextViewModel = value; + if (_SubContextViewModel != null) + { + _SubContextViewModel.IsActive = IsActive; + } + + OnPropertyChanged(nameof(SubContextViewModel)); + } + } + + internal ContextViewModel() + { + UIContext = SynchronizationContext.Current; + } + + internal ContextViewModel(MainViewModel Main) : this() + { + } + + internal ContextViewModel(MainViewModel Main, ContextViewModel SubContext) : this(Main) + { + SubContextViewModel = SubContext; + } + + internal bool IsActive { get; set; } = false; + + internal virtual void EvaluateViewState() + { + } + + internal void Activate() + { + IsActive = true; + EvaluateViewState(); + SubContextViewModel?.Activate(); + } + + internal void ActivateSubContext(ContextViewModel NewSubContext) + { + if (_SubContextViewModel != null) + { + _SubContextViewModel.IsActive = false; + } + + if (NewSubContext != null) + { + if (IsActive) + { + NewSubContext.Activate(); + } + else + { + NewSubContext.IsActive = false; + } + } + SubContextViewModel = NewSubContext; + } + + internal void SetWorkingStatus(string Message, string SubMessage, ulong? MaxProgressValue, bool ShowAnimation = true, WPinternalsStatus Status = WPinternalsStatus.Undefined) + { + ActivateSubContext(new BusyViewModel(Message, SubMessage, MaxProgressValue, UIContext: UIContext, ShowAnimation: ShowAnimation, ShowRebootHelp: Status == WPinternalsStatus.WaitingForManualReset)); + } + + internal void UpdateWorkingStatus(string Message, string SubMessage, ulong? CurrentProgressValue, WPinternalsStatus Status = WPinternalsStatus.Undefined) + { + if (SubContextViewModel is BusyViewModel Busy) + { + if (Message != null) + { + Busy.Message = Message; + Busy.SubMessage = SubMessage; + } + if ((CurrentProgressValue != null) && (Busy.ProgressUpdater != null)) + { + try + { + Busy.ProgressUpdater.SetProgress((ulong)CurrentProgressValue); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + } + Busy.SetShowRebootHelp(Status == WPinternalsStatus.WaitingForManualReset); + } + } + } +} diff --git a/ViewModels/DisclaimerAndNdaViewModel.cs b/WPinternals/ViewModels/DisclaimerAndNdaViewModel.cs similarity index 97% rename from ViewModels/DisclaimerAndNdaViewModel.cs rename to WPinternals/ViewModels/DisclaimerAndNdaViewModel.cs index 344e0d3..c78b0cc 100644 --- a/ViewModels/DisclaimerAndNdaViewModel.cs +++ b/WPinternals/ViewModels/DisclaimerAndNdaViewModel.cs @@ -1,60 +1,60 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using Microsoft.Win32; -using System; -using System.Windows; - -namespace WPinternals -{ - internal class DisclaimerAndNdaViewModel : ContextViewModel - { - private readonly Action Accepted; - - internal DisclaimerAndNdaViewModel(Action Accepted) - : base() - { - this.Accepted = Accepted; - } - - private DelegateCommand _ExitCommand = null; - public DelegateCommand ExitCommand - { - get - { - return _ExitCommand ??= new DelegateCommand(() => Application.Current.Shutdown()); - } - } - - private DelegateCommand _ContinueCommand = null; - public DelegateCommand ContinueCommand - { - get - { - return _ContinueCommand ??= new DelegateCommand(() => - { - Registry.CurrentUser.OpenSubKey("Software\\WPInternals", true).SetValue("DisclaimerAccepted", 1, RegistryValueKind.DWord); - Registry.CurrentUser.OpenSubKey("Software\\WPInternals", true).SetValue("NdaAccepted", 1, RegistryValueKind.DWord); - Accepted(); - }); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32; +using System; +using System.Windows; + +namespace WPinternals +{ + internal class DisclaimerAndNdaViewModel : ContextViewModel + { + private readonly Action Accepted; + + internal DisclaimerAndNdaViewModel(Action Accepted) + : base() + { + this.Accepted = Accepted; + } + + private DelegateCommand _ExitCommand = null; + public DelegateCommand ExitCommand + { + get + { + return _ExitCommand ??= new DelegateCommand(() => Application.Current.Shutdown()); + } + } + + private DelegateCommand _ContinueCommand = null; + public DelegateCommand ContinueCommand + { + get + { + return _ContinueCommand ??= new DelegateCommand(() => + { + Registry.CurrentUser.OpenSubKey("Software\\WPInternals", true).SetValue("DisclaimerAccepted", 1, RegistryValueKind.DWord); + Registry.CurrentUser.OpenSubKey("Software\\WPInternals", true).SetValue("NdaAccepted", 1, RegistryValueKind.DWord); + Accepted(); + }); + } + } + } +} diff --git a/ViewModels/DisclaimerViewModel.cs b/WPinternals/ViewModels/DisclaimerViewModel.cs similarity index 97% rename from ViewModels/DisclaimerViewModel.cs rename to WPinternals/ViewModels/DisclaimerViewModel.cs index 1ecc3b7..f460bfd 100644 --- a/ViewModels/DisclaimerViewModel.cs +++ b/WPinternals/ViewModels/DisclaimerViewModel.cs @@ -1,61 +1,61 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using Microsoft.Win32; -using System; -using System.Windows; - -namespace WPinternals -{ - internal delegate void DisclaimerAcceptedHandler(); - - internal class DisclaimerViewModel : ContextViewModel - { - private readonly Action Accepted; - - internal DisclaimerViewModel(Action Accepted) - : base() - { - this.Accepted = Accepted; - } - - private DelegateCommand _ExitCommand = null; - public DelegateCommand ExitCommand - { - get - { - return _ExitCommand ??= new DelegateCommand(() => Application.Current.Shutdown()); - } - } - - private DelegateCommand _ContinueCommand = null; - public DelegateCommand ContinueCommand - { - get - { - return _ContinueCommand ??= new DelegateCommand(() => - { - Registry.CurrentUser.OpenSubKey("Software\\WPInternals", true).SetValue("DisclaimerAccepted", 1, RegistryValueKind.DWord); - Accepted(); - }); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32; +using System; +using System.Windows; + +namespace WPinternals +{ + internal delegate void DisclaimerAcceptedHandler(); + + internal class DisclaimerViewModel : ContextViewModel + { + private readonly Action Accepted; + + internal DisclaimerViewModel(Action Accepted) + : base() + { + this.Accepted = Accepted; + } + + private DelegateCommand _ExitCommand = null; + public DelegateCommand ExitCommand + { + get + { + return _ExitCommand ??= new DelegateCommand(() => Application.Current.Shutdown()); + } + } + + private DelegateCommand _ContinueCommand = null; + public DelegateCommand ContinueCommand + { + get + { + return _ContinueCommand ??= new DelegateCommand(() => + { + Registry.CurrentUser.OpenSubKey("Software\\WPInternals", true).SetValue("DisclaimerAccepted", 1, RegistryValueKind.DWord); + Accepted(); + }); + } + } + } +} diff --git a/ViewModels/DownloadsViewModel.cs b/WPinternals/ViewModels/DownloadsViewModel.cs similarity index 97% rename from ViewModels/DownloadsViewModel.cs rename to WPinternals/ViewModels/DownloadsViewModel.cs index f4897e0..9d5b002 100644 --- a/ViewModels/DownloadsViewModel.cs +++ b/WPinternals/ViewModels/DownloadsViewModel.cs @@ -1,965 +1,965 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Data; - -namespace WPinternals -{ - internal class DownloadsViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel Notifier; - private readonly Timer SpeedTimer; - private bool IsSearching = false; - - internal DownloadsViewModel(PhoneNotifierViewModel Notifier) - { - IsSwitchingInterface = false; - IsFlashModeOperation = false; - this.Notifier = Notifier; - Notifier.NewDeviceArrived += Notifier_NewDeviceArrived; - - RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\WPInternals", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\WPInternals"); - - DownloadFolder = (string)Key.GetValue("DownloadFolder", @"C:\ProgramData\WPinternals\Repository"); - Key.Close(); - - SpeedTimer = new Timer(TimerCallback, this, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); - - AddFFUCommand = new DelegateCommand(() => - { - string FFUPath = null; - - OpenFileDialog dlg = new(); - dlg.DefaultExt = ".ffu"; // Default file extension - dlg.Filter = "ROM images (.ffu)|*.ffu"; // Filter files by extension - - bool? result = dlg.ShowDialog(); - - if (result == true) - { - FFUPath = dlg.FileName; - string FFUFile = Path.GetFileName(FFUPath); - - try - { - App.Config.AddFfuToRepository(FFUPath); - App.Config.WriteConfig(); - LastStatusText = "File \"" + FFUFile + "\" was added to the repository."; - } - catch (WPinternalsException Ex) - { - LastStatusText = "Error: " + Ex.Message + ". File \"" + FFUFile + "\" was not added."; - } - catch - { - LastStatusText = "Error: File \"" + FFUFile + "\" was not added."; - } - } - else - { - LastStatusText = null; - } - }); - } - - private string _LastStatusText = null; - public string LastStatusText - { - get - { - return _LastStatusText; - } - set - { - _LastStatusText = value; - OnPropertyChanged(nameof(LastStatusText)); - } - } - - internal static void TimerCallback(object State) - { - foreach (DownloadEntry Entry in App.DownloadManager.DownloadList) - { - if (Entry.SpeedIndex >= 0) - { - int ArrayIndex = (int)(Entry.SpeedIndex % 10); - Entry.Speeds[ArrayIndex] = Entry.BytesReceived - Entry.LastBytesReceived; - int Count = (int)((Entry.SpeedIndex + 1) > 10 ? 10 : (Entry.SpeedIndex + 1)); - long Sum = 0; - for (int i = 0; i < Count; i++) - { - Sum += Entry.Speeds[i]; - } - - Entry.Speed = Sum / Count; - Entry.TimeLeft = Entry.Speed < 1000 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds((Entry.Size - Entry.BytesReceived) / Entry.Speed); - } - Entry.LastBytesReceived = Entry.BytesReceived; - Entry.SpeedIndex++; - } - } - - private void Notifier_NewDeviceArrived(ArrivalEventArgs Args) - { - EvaluateViewState(); - } - - internal static long GetFileLengthFromURL(string URL) - { - long Length = 0; - HttpWebRequest req = (HttpWebRequest)WebRequest.Create(URL); - req.Method = "HEAD"; - req.ServicePoint.ConnectionLimit = 10; - using (WebResponse resp = req.GetResponse()) - { - long.TryParse(resp.Headers.Get("Content-Length"), out Length); - } - return Length; - } - - internal static string GetFileNameFromURL(string URL) - { - string FileName = Path.GetFileName(URL); - int End = FileName.IndexOf('?'); - if (End >= 0) - { - FileName = FileName.Substring(0, End); - } - - return FileName; - } - - private void Search() - { - if (IsSearching) - { - return; - } - - IsSearching = true; - - SynchronizationContext UIContext = SynchronizationContext.Current; - SearchResultList.Clear(); - - new Thread(() => - { - string FFUURL = null; - string[] EmergencyURLs = null; - try - { - string TempProductType = ProductType.ToUpper(); - if ((TempProductType?.StartsWith("RM") == true) && !TempProductType.StartsWith("RM-")) - { - TempProductType = "RM-" + TempProductType[2..]; - } - - ProductType = TempProductType; - FFUURL = LumiaDownloadModel.SearchFFU(ProductType, ProductCode, OperatorCode, out TempProductType); - if (TempProductType != null) - { - ProductType = TempProductType; - } - - if (ProductType != null) - { - EmergencyURLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - } - } - catch { } - - UIContext.Post(s => - { - if (FFUURL != null) - { - SearchResultList.Add(new SearchResult(FFUURL, ProductType, FFUDownloaded, null)); - } - - if (EmergencyURLs != null) - { - SearchResultList.Add(new SearchResult(ProductType + " emergency-files", EmergencyURLs, ProductType, EmergencyDownloaded, ProductType)); - } - }, null); - - IsSearching = false; - }).Start(); - } - - internal void Download(string URL, string Category, Action Callback, object State = null) - { - string Folder = Category == null ? DownloadFolder : Path.Combine(DownloadFolder, Category); - DownloadList.Add(new DownloadEntry(URL, Folder, null, Callback, State)); - } - - internal void Download(string[] URLs, string Category, Action Callback, object State = null) - { - string Folder = Category == null ? DownloadFolder : Path.Combine(DownloadFolder, Category); - foreach (string URL in URLs) - { - DownloadList.Add(new DownloadEntry(URL, Folder, URLs, Callback, State)); - } - } - - private void DownloadAll() - { - SynchronizationContext UIContext = SynchronizationContext.Current; - - new Thread(() => - { - string FFUURL = null; - string[] EmergencyURLs = null; - try - { - string TempProductType = ProductType.ToUpper(); - if ((TempProductType?.StartsWith("RM") == true) && !TempProductType.StartsWith("RM-")) - { - TempProductType = "RM-" + TempProductType[2..]; - } - - ProductType = TempProductType; - FFUURL = LumiaDownloadModel.SearchFFU(ProductType, ProductCode, OperatorCode, out TempProductType); - if (TempProductType != null) - { - ProductType = TempProductType; - } - - if (ProductType != null) - { - EmergencyURLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); - } - } - catch { } - - UIContext.Post(s => - { - if (FFUURL != null) - { - Download(FFUURL, ProductType, FFUDownloadedAndCheckSupported, null); - } - - if (EmergencyURLs != null) - { - Download(EmergencyURLs, ProductType, EmergencyDownloaded, ProductType); - } - }, null); - }).Start(); - } - - private void DownloadSelected() - { - foreach (SearchResult Result in SearchResultList.Where(r => r.IsSelected)) - { - App.DownloadManager.Download(Result.URLs, Result.Category, Result.Callback, Result.State); - } - } - - private void FFUDownloaded(string[] Files, object State) - { - App.Config.AddFfuToRepository(Files[0]); - } - - private void FFUDownloadedAndCheckSupported(string[] Files, object State) - { - App.Config.AddFfuToRepository(Files[0]); - - if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - const string ProductType2 = "RM-1085"; - string URL = LumiaDownloadModel.SearchFFU(ProductType2, null, null); - Download(URL, ProductType2, FFUDownloaded, null); - } - } - - private void EmergencyDownloaded(string[] Files, object State) - { - string Type = (string)State; - string ProgrammerPath = null; - string PayloadPath = null; - - for (int i = 0; i < Files.Length; i++) - { - if (Files[i].EndsWith(".ede", StringComparison.OrdinalIgnoreCase)) - { - ProgrammerPath = Files[i]; - } - - if (Files[i].EndsWith(".edp", StringComparison.OrdinalIgnoreCase)) - { - PayloadPath = Files[i]; - } - } - - if ((Type != null) && (ProgrammerPath != null) && (PayloadPath != null)) - { - App.Config.AddEmergencyToRepository(Type, ProgrammerPath, PayloadPath); - } - } - public ObservableCollection DownloadList { get; } = new(); - public ObservableCollection SearchResultList { get; } = new(); - - private DelegateCommand _DownloadSelectedCommand = null; - public DelegateCommand DownloadSelectedCommand - { - get - { - return _DownloadSelectedCommand ??= new DelegateCommand(() => DownloadSelected()); - } - } - - private DelegateCommand _SearchCommand = null; - public DelegateCommand SearchCommand - { - get - { - return _SearchCommand ??= new DelegateCommand(() => Search()); - } - } - - private DelegateCommand _DownloadAllCommand = null; - public DelegateCommand DownloadAllCommand - { - get - { - return _DownloadAllCommand ??= new DelegateCommand(() => DownloadAll()); - } - } - - private string _DownloadFolder = null; - public string DownloadFolder - { - get - { - return _DownloadFolder; - } - set - { - if (_DownloadFolder != value) - { - _DownloadFolder = value; - - try - { - Directory.CreateDirectory(_DownloadFolder); - } - catch { } - if (!Directory.Exists(_DownloadFolder)) - { - _DownloadFolder = @"C:\ProgramData\WPinternals\Repository"; - Directory.CreateDirectory(_DownloadFolder); - } - - RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\WPInternals", true); - - if (_DownloadFolder == null) - { - if (Key.GetValue("DownloadFolder") != null) - { - Key.DeleteValue("DownloadFolder"); - } - } - else - { - Key.SetValue("DownloadFolder", _DownloadFolder); - } - - Key.Close(); - - OnPropertyChanged(nameof(DownloadFolder)); - } - } - } - - private string _ProductCode = null; - public string ProductCode - { - get - { - return _ProductCode; - } - set - { - if (_ProductCode != value) - { - _ProductCode = value; - - OnPropertyChanged(nameof(ProductCode)); - } - } - } - - private string _ProductType = null; - public string ProductType - { - get - { - return _ProductType; - } - set - { - if (_ProductType != value) - { - _ProductType = value; - - OnPropertyChanged(nameof(ProductType)); - } - } - } - - private string _OperatorCode = null; - public string OperatorCode - { - get - { - return _OperatorCode; - } - set - { - if (_OperatorCode != value) - { - _OperatorCode = value; - - OnPropertyChanged(nameof(OperatorCode)); - } - } - } - - internal override void EvaluateViewState() - { - if (!IsActive) - { - return; - } - - if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash) - { - NokiaFlashModel LumiaFlashModel = (NokiaFlashModel)Notifier.CurrentModel; - PhoneInfo Info = LumiaFlashModel.ReadPhoneInfo(); - ProductType = Info.Type; - OperatorCode = ""; - ProductCode = Info.ProductCode; - } - else if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) - { - NokiaPhoneModel LumiaNormalModel = (NokiaPhoneModel)Notifier.CurrentModel; - OperatorCode = LumiaNormalModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // Example: 000-NL - string TempProductType = LumiaNormalModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 - if (TempProductType.Contains('_')) - { - TempProductType = TempProductType.Substring(0, TempProductType.IndexOf('_')); - } - - ProductType = TempProductType; - ProductCode = LumiaNormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 - } - } - public DelegateCommand AddFFUCommand { get; } = null; - } - - internal enum DownloadStatus - { - Downloading, - Ready, - Failed - }; - - internal class DownloadEntry : INotifyPropertyChanged - { - private readonly SynchronizationContext UIContext; - public event PropertyChangedEventHandler PropertyChanged = delegate { }; - internal Action Callback; - internal object State; - internal string URL; - internal string[] URLCollection; - internal string Folder; - internal HttpClient Client; - internal long SpeedIndex = -1; - internal long[] Speeds = new long[10]; - internal long LastBytesReceived; - internal long BytesReceived; - - internal DownloadEntry(string URL, string Folder, string[] URLCollection, Action Callback, object State) - { - UIContext = SynchronizationContext.Current; - this.URL = URL; - this.Callback = Callback; - this.State = State; - this.URLCollection = URLCollection; - this.Folder = Folder; - Directory.CreateDirectory(Folder); - Name = DownloadsViewModel.GetFileNameFromURL(URL); - Uri Uri = new(URL); - Status = DownloadStatus.Downloading; - new Thread(() => - { - Size = DownloadsViewModel.GetFileLengthFromURL(URL); - - Client = new HttpClient(); - _ = Client.DownloadFileAsync(Uri, Path.Combine(Folder, DownloadsViewModel.GetFileNameFromURL(Uri.LocalPath)), Client_DownloadProgressChanged, Client_DownloadFileCompleted); - }).Start(); - } - - private void Client_DownloadFileCompleted(bool Error) - { - void Finish() - { - Status = Error ? DownloadStatus.Failed : DownloadStatus.Ready; - App.DownloadManager.DownloadList.Remove(this); - if (Status == DownloadStatus.Ready) - { - if (URLCollection?.Any(c => App.DownloadManager.DownloadList.Any(d => d.URL == c)) != true) // if there are no files left to download from this collection, then call the callback-function. - { - string[] Files; - if (URLCollection == null) - { - Files = new string[1]; - Files[0] = Path.Combine(Folder, DownloadsViewModel.GetFileNameFromURL(URL)); - } - else - { - Files = new string[URLCollection.Length]; - for (int i = 0; i < URLCollection.Length; i++) - { - Files[i] = Path.Combine(Folder, DownloadsViewModel.GetFileNameFromURL(URLCollection[i])); - } - } - - Callback(Files, State); - } - } - } - - UIContext?.Post(d => Finish(), null); - } - - private void Client_DownloadProgressChanged(HttpClientDownloadProgress e) - { - BytesReceived = e.BytesReceived; - Progress = e.ProgressPercentage; - } - - protected void OnPropertyChanged(string propertyName) - { - if (this.PropertyChanged != null) - { - if (SynchronizationContext.Current == UIContext) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } - else - { - UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); - } - } - } - - private DownloadStatus _Status; - public DownloadStatus Status - { - get - { - return _Status; - } - set - { - if (_Status != value) - { - _Status = value; - OnPropertyChanged(nameof(Status)); - } - } - } - - private string _Name; - public string Name - { - get - { - return _Name; - } - set - { - if (_Name != value) - { - _Name = value; - OnPropertyChanged(nameof(Name)); - } - } - } - - private long _Size; - public long Size - { - get - { - return _Size; - } - set - { - if (_Size != value) - { - _Size = value; - OnPropertyChanged(nameof(Size)); - } - } - } - - private TimeSpan _TimeLeft; - public TimeSpan TimeLeft - { - get - { - return _TimeLeft; - } - set - { - if (_TimeLeft != value) - { - _TimeLeft = value; - OnPropertyChanged(nameof(TimeLeft)); - } - } - } - - private double _Speed; - public double Speed - { - get - { - return _Speed; - } - set - { - if (_Speed != value) - { - _Speed = value; - OnPropertyChanged(nameof(Speed)); - } - } - } - - private int _Progress; - public int Progress - { - get - { - return _Progress; - } - set - { - if (_Progress != value) - { - _Progress = value; - OnPropertyChanged(nameof(Progress)); - } - } - } - } - - internal class SearchResult : INotifyPropertyChanged - { - private readonly SynchronizationContext UIContext; - public event PropertyChangedEventHandler PropertyChanged; - internal string[] URLs; - internal Action Callback; - internal object State; - internal string Category; - - internal SearchResult(string URL, string Category, Action Callback, object State) - { - UIContext = SynchronizationContext.Current; - URLs = new string[1]; - URLs[0] = URL; - Name = DownloadsViewModel.GetFileNameFromURL(URL); - this.Callback = Callback; - this.State = State; - this.Category = Category; - GetSize(); - } - - internal SearchResult(string Name, string[] URLs, string Category, Action Callback, object State) - { - UIContext = SynchronizationContext.Current; - this.URLs = URLs; - this.Name = Name; - this.Callback = Callback; - this.State = State; - this.Category = Category; - GetSize(); - } - - private void GetSize() - { - new Thread(() => - { - long CalcSize = 0; - foreach (string URL in URLs) - { - CalcSize += DownloadsViewModel.GetFileLengthFromURL(URL); - } - Size = CalcSize; - }).Start(); - } - - protected void OnPropertyChanged(string propertyName) - { - if (this.PropertyChanged != null) - { - if (SynchronizationContext.Current == UIContext) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } - else - { - UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); - } - } - } - - private string _Name; - public string Name - { - get - { - return _Name; - } - set - { - if (_Name != value) - { - _Name = value; - OnPropertyChanged(nameof(Name)); - } - } - } - - private long _Size; - public long Size - { - get - { - return _Size; - } - set - { - if (_Size != value) - { - _Size = value; - OnPropertyChanged(nameof(Size)); - } - } - } - - private bool _IsSelected; - public bool IsSelected - { - get - { - return _IsSelected; - } - set - { - if (_IsSelected != value) - { - _IsSelected = value; - OnPropertyChanged(nameof(IsSelected)); - } - } - } - } - - public class HttpClientDownloadProgress - { - // - // Summary: - // Gets the asynchronous task progress percentage. - // - // Returns: - // A percentage value indicating the asynchronous task progress. - public int ProgressPercentage { get; } - - // - // Summary: - // Gets the number of bytes received. - // - // Returns: - // An System.Int64 value that indicates the number of bytes received. - public long BytesReceived { get; } - - // - // Summary: - // Gets the total number of bytes in a System.Net.WebClient data download operation. - // - // Returns: - // An System.Int64 value that indicates the number of bytes that will be received. - public long TotalBytesToReceive { get; } - - internal HttpClientDownloadProgress(long BytesReceived, long TotalBytesToReceive) - { - this.TotalBytesToReceive = TotalBytesToReceive; - this.BytesReceived = BytesReceived; - ProgressPercentage = (int)Math.Round((float)BytesReceived / TotalBytesToReceive * 100f); - } - } - - public static class HttpClientProgressExtensions - { - public static async Task DownloadFileAsync(this HttpClient client, Uri address, string fileName, Action progress = null, Action completed = null, CancellationToken cancellationToken = default(CancellationToken)) - { - try - { - using FileStream destination = File.Create(fileName); - using HttpResponseMessage response = await client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead); - long? contentLength = response.Content.Headers.ContentLength; - using Stream download = await response.Content.ReadAsStreamAsync(); - - if (progress is null || !contentLength.HasValue) - { - await download.CopyToAsync(destination); - if (completed != null) - completed(true); - return; - } - - Progress progressWrapper = new Progress(totalBytes => progress(new HttpClientDownloadProgress(totalBytes, contentLength.Value))); - await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken); - - if (completed != null) - completed(true); - } - catch - { - if (completed != null) - completed(false); - } - } - - private static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default(CancellationToken)) - { - if (bufferSize < 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - if (source is null) - throw new ArgumentNullException(nameof(source)); - if (!source.CanRead) - throw new InvalidOperationException($"'{nameof(source)}' is not readable."); - if (destination == null) - throw new ArgumentNullException(nameof(destination)); - if (!destination.CanWrite) - throw new InvalidOperationException($"'{nameof(destination)}' is not writable."); - - byte[] buffer = new byte[bufferSize]; - long totalBytesRead = 0; - int bytesRead; - while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - totalBytesRead += bytesRead; - progress?.Report(totalBytesRead); - } - } - } - - public class DownloaderNameConvertor : IValueConverter - { - public object Convert(object value, Type targetType, - object parameter, CultureInfo culture) - { - return Path.GetFileNameWithoutExtension((string)value); - } - - public object ConvertBack(object value, Type targetType, - object parameter, CultureInfo culture) - { - return Binding.DoNothing; - } - } - - public class DownloaderSizeConvertor : IValueConverter - { - public object Convert(object value, Type targetType, - object parameter, CultureInfo culture) - { - long? Size = value as long?; - if (Size < 1024) - { - return Size + " B"; - } - - if (Size < (1024 * 1024)) - { - return Math.Round((double)Size / 1024, 0) + " KB"; - } - - return Math.Round((double)Size / 1024 / 1024, 0) + " MB"; - } - - public object ConvertBack(object value, Type targetType, - object parameter, CultureInfo culture) - { - return Binding.DoNothing; - } - } - - public class DownloaderSpeedConvertor : IValueConverter - { - public object Convert(object value, Type targetType, - object parameter, CultureInfo culture) - { - return ((int)((double)value / 1024)).ToString() + " KB/s"; - } - - public object ConvertBack(object value, Type targetType, - object parameter, CultureInfo culture) - { - return Binding.DoNothing; - } - } - - public class DownloaderTimeRemainingConvertor : IValueConverter - { - public object Convert(object value, Type targetType, - object parameter, CultureInfo culture) - { - TimeSpan TimeLeft = (TimeSpan)value; - if (TimeLeft == Timeout.InfiniteTimeSpan) - { - return ""; - } - - return TimeLeft.ToString(@"h\:mm\:ss"); - } - - public object ConvertBack(object value, Type targetType, - object parameter, CultureInfo culture) - { - return Binding.DoNothing; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace WPinternals +{ + internal class DownloadsViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel Notifier; + private readonly Timer SpeedTimer; + private bool IsSearching = false; + + internal DownloadsViewModel(PhoneNotifierViewModel Notifier) + { + IsSwitchingInterface = false; + IsFlashModeOperation = false; + this.Notifier = Notifier; + Notifier.NewDeviceArrived += Notifier_NewDeviceArrived; + + RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\WPInternals", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\WPInternals"); + + DownloadFolder = (string)Key.GetValue("DownloadFolder", @"C:\ProgramData\WPinternals\Repository"); + Key.Close(); + + SpeedTimer = new Timer(TimerCallback, this, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); + + AddFFUCommand = new DelegateCommand(() => + { + string FFUPath = null; + + OpenFileDialog dlg = new(); + dlg.DefaultExt = ".ffu"; // Default file extension + dlg.Filter = "ROM images (.ffu)|*.ffu"; // Filter files by extension + + bool? result = dlg.ShowDialog(); + + if (result == true) + { + FFUPath = dlg.FileName; + string FFUFile = Path.GetFileName(FFUPath); + + try + { + App.Config.AddFfuToRepository(FFUPath); + App.Config.WriteConfig(); + LastStatusText = "File \"" + FFUFile + "\" was added to the repository."; + } + catch (WPinternalsException Ex) + { + LastStatusText = "Error: " + Ex.Message + ". File \"" + FFUFile + "\" was not added."; + } + catch + { + LastStatusText = "Error: File \"" + FFUFile + "\" was not added."; + } + } + else + { + LastStatusText = null; + } + }); + } + + private string _LastStatusText = null; + public string LastStatusText + { + get + { + return _LastStatusText; + } + set + { + _LastStatusText = value; + OnPropertyChanged(nameof(LastStatusText)); + } + } + + internal static void TimerCallback(object State) + { + foreach (DownloadEntry Entry in App.DownloadManager.DownloadList) + { + if (Entry.SpeedIndex >= 0) + { + int ArrayIndex = (int)(Entry.SpeedIndex % 10); + Entry.Speeds[ArrayIndex] = Entry.BytesReceived - Entry.LastBytesReceived; + int Count = (int)((Entry.SpeedIndex + 1) > 10 ? 10 : (Entry.SpeedIndex + 1)); + long Sum = 0; + for (int i = 0; i < Count; i++) + { + Sum += Entry.Speeds[i]; + } + + Entry.Speed = Sum / Count; + Entry.TimeLeft = Entry.Speed < 1000 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds((Entry.Size - Entry.BytesReceived) / Entry.Speed); + } + Entry.LastBytesReceived = Entry.BytesReceived; + Entry.SpeedIndex++; + } + } + + private void Notifier_NewDeviceArrived(ArrivalEventArgs Args) + { + EvaluateViewState(); + } + + internal static long GetFileLengthFromURL(string URL) + { + long Length = 0; + HttpWebRequest req = (HttpWebRequest)WebRequest.Create(URL); + req.Method = "HEAD"; + req.ServicePoint.ConnectionLimit = 10; + using (WebResponse resp = req.GetResponse()) + { + long.TryParse(resp.Headers.Get("Content-Length"), out Length); + } + return Length; + } + + internal static string GetFileNameFromURL(string URL) + { + string FileName = Path.GetFileName(URL); + int End = FileName.IndexOf('?'); + if (End >= 0) + { + FileName = FileName.Substring(0, End); + } + + return FileName; + } + + private void Search() + { + if (IsSearching) + { + return; + } + + IsSearching = true; + + SynchronizationContext UIContext = SynchronizationContext.Current; + SearchResultList.Clear(); + + new Thread(() => + { + string FFUURL = null; + string[] EmergencyURLs = null; + try + { + string TempProductType = ProductType.ToUpper(); + if ((TempProductType?.StartsWith("RM") == true) && !TempProductType.StartsWith("RM-")) + { + TempProductType = "RM-" + TempProductType[2..]; + } + + ProductType = TempProductType; + FFUURL = LumiaDownloadModel.SearchFFU(ProductType, ProductCode, OperatorCode, out TempProductType); + if (TempProductType != null) + { + ProductType = TempProductType; + } + + if (ProductType != null) + { + EmergencyURLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + } + } + catch { } + + UIContext.Post(s => + { + if (FFUURL != null) + { + SearchResultList.Add(new SearchResult(FFUURL, ProductType, FFUDownloaded, null)); + } + + if (EmergencyURLs != null) + { + SearchResultList.Add(new SearchResult(ProductType + " emergency-files", EmergencyURLs, ProductType, EmergencyDownloaded, ProductType)); + } + }, null); + + IsSearching = false; + }).Start(); + } + + internal void Download(string URL, string Category, Action Callback, object State = null) + { + string Folder = Category == null ? DownloadFolder : Path.Combine(DownloadFolder, Category); + DownloadList.Add(new DownloadEntry(URL, Folder, null, Callback, State)); + } + + internal void Download(string[] URLs, string Category, Action Callback, object State = null) + { + string Folder = Category == null ? DownloadFolder : Path.Combine(DownloadFolder, Category); + foreach (string URL in URLs) + { + DownloadList.Add(new DownloadEntry(URL, Folder, URLs, Callback, State)); + } + } + + private void DownloadAll() + { + SynchronizationContext UIContext = SynchronizationContext.Current; + + new Thread(() => + { + string FFUURL = null; + string[] EmergencyURLs = null; + try + { + string TempProductType = ProductType.ToUpper(); + if ((TempProductType?.StartsWith("RM") == true) && !TempProductType.StartsWith("RM-")) + { + TempProductType = "RM-" + TempProductType[2..]; + } + + ProductType = TempProductType; + FFUURL = LumiaDownloadModel.SearchFFU(ProductType, ProductCode, OperatorCode, out TempProductType); + if (TempProductType != null) + { + ProductType = TempProductType; + } + + if (ProductType != null) + { + EmergencyURLs = LumiaDownloadModel.SearchEmergencyFiles(ProductType); + } + } + catch { } + + UIContext.Post(s => + { + if (FFUURL != null) + { + Download(FFUURL, ProductType, FFUDownloadedAndCheckSupported, null); + } + + if (EmergencyURLs != null) + { + Download(EmergencyURLs, ProductType, EmergencyDownloaded, ProductType); + } + }, null); + }).Start(); + } + + private void DownloadSelected() + { + foreach (SearchResult Result in SearchResultList.Where(r => r.IsSelected)) + { + App.DownloadManager.Download(Result.URLs, Result.Category, Result.Callback, Result.State); + } + } + + private void FFUDownloaded(string[] Files, object State) + { + App.Config.AddFfuToRepository(Files[0]); + } + + private void FFUDownloadedAndCheckSupported(string[] Files, object State) + { + App.Config.AddFfuToRepository(Files[0]); + + if (!App.Config.FFURepository.Any(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + const string ProductType2 = "RM-1085"; + string URL = LumiaDownloadModel.SearchFFU(ProductType2, null, null); + Download(URL, ProductType2, FFUDownloaded, null); + } + } + + private void EmergencyDownloaded(string[] Files, object State) + { + string Type = (string)State; + string ProgrammerPath = null; + string PayloadPath = null; + + for (int i = 0; i < Files.Length; i++) + { + if (Files[i].EndsWith(".ede", StringComparison.OrdinalIgnoreCase)) + { + ProgrammerPath = Files[i]; + } + + if (Files[i].EndsWith(".edp", StringComparison.OrdinalIgnoreCase)) + { + PayloadPath = Files[i]; + } + } + + if ((Type != null) && (ProgrammerPath != null) && (PayloadPath != null)) + { + App.Config.AddEmergencyToRepository(Type, ProgrammerPath, PayloadPath); + } + } + public ObservableCollection DownloadList { get; } = new(); + public ObservableCollection SearchResultList { get; } = new(); + + private DelegateCommand _DownloadSelectedCommand = null; + public DelegateCommand DownloadSelectedCommand + { + get + { + return _DownloadSelectedCommand ??= new DelegateCommand(() => DownloadSelected()); + } + } + + private DelegateCommand _SearchCommand = null; + public DelegateCommand SearchCommand + { + get + { + return _SearchCommand ??= new DelegateCommand(() => Search()); + } + } + + private DelegateCommand _DownloadAllCommand = null; + public DelegateCommand DownloadAllCommand + { + get + { + return _DownloadAllCommand ??= new DelegateCommand(() => DownloadAll()); + } + } + + private string _DownloadFolder = null; + public string DownloadFolder + { + get + { + return _DownloadFolder; + } + set + { + if (_DownloadFolder != value) + { + _DownloadFolder = value; + + try + { + Directory.CreateDirectory(_DownloadFolder); + } + catch { } + if (!Directory.Exists(_DownloadFolder)) + { + _DownloadFolder = @"C:\ProgramData\WPinternals\Repository"; + Directory.CreateDirectory(_DownloadFolder); + } + + RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\WPInternals", true); + + if (_DownloadFolder == null) + { + if (Key.GetValue("DownloadFolder") != null) + { + Key.DeleteValue("DownloadFolder"); + } + } + else + { + Key.SetValue("DownloadFolder", _DownloadFolder); + } + + Key.Close(); + + OnPropertyChanged(nameof(DownloadFolder)); + } + } + } + + private string _ProductCode = null; + public string ProductCode + { + get + { + return _ProductCode; + } + set + { + if (_ProductCode != value) + { + _ProductCode = value; + + OnPropertyChanged(nameof(ProductCode)); + } + } + } + + private string _ProductType = null; + public string ProductType + { + get + { + return _ProductType; + } + set + { + if (_ProductType != value) + { + _ProductType = value; + + OnPropertyChanged(nameof(ProductType)); + } + } + } + + private string _OperatorCode = null; + public string OperatorCode + { + get + { + return _OperatorCode; + } + set + { + if (_OperatorCode != value) + { + _OperatorCode = value; + + OnPropertyChanged(nameof(OperatorCode)); + } + } + } + + internal override void EvaluateViewState() + { + if (!IsActive) + { + return; + } + + if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Flash) + { + NokiaFlashModel LumiaFlashModel = (NokiaFlashModel)Notifier.CurrentModel; + PhoneInfo Info = LumiaFlashModel.ReadPhoneInfo(); + ProductType = Info.Type; + OperatorCode = ""; + ProductCode = Info.ProductCode; + } + else if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_Normal) + { + NokiaPhoneModel LumiaNormalModel = (NokiaPhoneModel)Notifier.CurrentModel; + OperatorCode = LumiaNormalModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // Example: 000-NL + string TempProductType = LumiaNormalModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 + if (TempProductType.Contains('_')) + { + TempProductType = TempProductType.Substring(0, TempProductType.IndexOf('_')); + } + + ProductType = TempProductType; + ProductCode = LumiaNormalModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 + } + } + public DelegateCommand AddFFUCommand { get; } = null; + } + + internal enum DownloadStatus + { + Downloading, + Ready, + Failed + }; + + internal class DownloadEntry : INotifyPropertyChanged + { + private readonly SynchronizationContext UIContext; + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + internal Action Callback; + internal object State; + internal string URL; + internal string[] URLCollection; + internal string Folder; + internal HttpClient Client; + internal long SpeedIndex = -1; + internal long[] Speeds = new long[10]; + internal long LastBytesReceived; + internal long BytesReceived; + + internal DownloadEntry(string URL, string Folder, string[] URLCollection, Action Callback, object State) + { + UIContext = SynchronizationContext.Current; + this.URL = URL; + this.Callback = Callback; + this.State = State; + this.URLCollection = URLCollection; + this.Folder = Folder; + Directory.CreateDirectory(Folder); + Name = DownloadsViewModel.GetFileNameFromURL(URL); + Uri Uri = new(URL); + Status = DownloadStatus.Downloading; + new Thread(() => + { + Size = DownloadsViewModel.GetFileLengthFromURL(URL); + + Client = new HttpClient(); + _ = Client.DownloadFileAsync(Uri, Path.Combine(Folder, DownloadsViewModel.GetFileNameFromURL(Uri.LocalPath)), Client_DownloadProgressChanged, Client_DownloadFileCompleted); + }).Start(); + } + + private void Client_DownloadFileCompleted(bool Error) + { + void Finish() + { + Status = Error ? DownloadStatus.Failed : DownloadStatus.Ready; + App.DownloadManager.DownloadList.Remove(this); + if (Status == DownloadStatus.Ready) + { + if (URLCollection?.Any(c => App.DownloadManager.DownloadList.Any(d => d.URL == c)) != true) // if there are no files left to download from this collection, then call the callback-function. + { + string[] Files; + if (URLCollection == null) + { + Files = new string[1]; + Files[0] = Path.Combine(Folder, DownloadsViewModel.GetFileNameFromURL(URL)); + } + else + { + Files = new string[URLCollection.Length]; + for (int i = 0; i < URLCollection.Length; i++) + { + Files[i] = Path.Combine(Folder, DownloadsViewModel.GetFileNameFromURL(URLCollection[i])); + } + } + + Callback(Files, State); + } + } + } + + UIContext?.Post(d => Finish(), null); + } + + private void Client_DownloadProgressChanged(HttpClientDownloadProgress e) + { + BytesReceived = e.BytesReceived; + Progress = e.ProgressPercentage; + } + + protected void OnPropertyChanged(string propertyName) + { + if (this.PropertyChanged != null) + { + if (SynchronizationContext.Current == UIContext) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + else + { + UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); + } + } + } + + private DownloadStatus _Status; + public DownloadStatus Status + { + get + { + return _Status; + } + set + { + if (_Status != value) + { + _Status = value; + OnPropertyChanged(nameof(Status)); + } + } + } + + private string _Name; + public string Name + { + get + { + return _Name; + } + set + { + if (_Name != value) + { + _Name = value; + OnPropertyChanged(nameof(Name)); + } + } + } + + private long _Size; + public long Size + { + get + { + return _Size; + } + set + { + if (_Size != value) + { + _Size = value; + OnPropertyChanged(nameof(Size)); + } + } + } + + private TimeSpan _TimeLeft; + public TimeSpan TimeLeft + { + get + { + return _TimeLeft; + } + set + { + if (_TimeLeft != value) + { + _TimeLeft = value; + OnPropertyChanged(nameof(TimeLeft)); + } + } + } + + private double _Speed; + public double Speed + { + get + { + return _Speed; + } + set + { + if (_Speed != value) + { + _Speed = value; + OnPropertyChanged(nameof(Speed)); + } + } + } + + private int _Progress; + public int Progress + { + get + { + return _Progress; + } + set + { + if (_Progress != value) + { + _Progress = value; + OnPropertyChanged(nameof(Progress)); + } + } + } + } + + internal class SearchResult : INotifyPropertyChanged + { + private readonly SynchronizationContext UIContext; + public event PropertyChangedEventHandler PropertyChanged; + internal string[] URLs; + internal Action Callback; + internal object State; + internal string Category; + + internal SearchResult(string URL, string Category, Action Callback, object State) + { + UIContext = SynchronizationContext.Current; + URLs = new string[1]; + URLs[0] = URL; + Name = DownloadsViewModel.GetFileNameFromURL(URL); + this.Callback = Callback; + this.State = State; + this.Category = Category; + GetSize(); + } + + internal SearchResult(string Name, string[] URLs, string Category, Action Callback, object State) + { + UIContext = SynchronizationContext.Current; + this.URLs = URLs; + this.Name = Name; + this.Callback = Callback; + this.State = State; + this.Category = Category; + GetSize(); + } + + private void GetSize() + { + new Thread(() => + { + long CalcSize = 0; + foreach (string URL in URLs) + { + CalcSize += DownloadsViewModel.GetFileLengthFromURL(URL); + } + Size = CalcSize; + }).Start(); + } + + protected void OnPropertyChanged(string propertyName) + { + if (this.PropertyChanged != null) + { + if (SynchronizationContext.Current == UIContext) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + else + { + UIContext.Post((s) => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); + } + } + } + + private string _Name; + public string Name + { + get + { + return _Name; + } + set + { + if (_Name != value) + { + _Name = value; + OnPropertyChanged(nameof(Name)); + } + } + } + + private long _Size; + public long Size + { + get + { + return _Size; + } + set + { + if (_Size != value) + { + _Size = value; + OnPropertyChanged(nameof(Size)); + } + } + } + + private bool _IsSelected; + public bool IsSelected + { + get + { + return _IsSelected; + } + set + { + if (_IsSelected != value) + { + _IsSelected = value; + OnPropertyChanged(nameof(IsSelected)); + } + } + } + } + + public class HttpClientDownloadProgress + { + // + // Summary: + // Gets the asynchronous task progress percentage. + // + // Returns: + // A percentage value indicating the asynchronous task progress. + public int ProgressPercentage { get; } + + // + // Summary: + // Gets the number of bytes received. + // + // Returns: + // An System.Int64 value that indicates the number of bytes received. + public long BytesReceived { get; } + + // + // Summary: + // Gets the total number of bytes in a System.Net.WebClient data download operation. + // + // Returns: + // An System.Int64 value that indicates the number of bytes that will be received. + public long TotalBytesToReceive { get; } + + internal HttpClientDownloadProgress(long BytesReceived, long TotalBytesToReceive) + { + this.TotalBytesToReceive = TotalBytesToReceive; + this.BytesReceived = BytesReceived; + ProgressPercentage = (int)Math.Round((float)BytesReceived / TotalBytesToReceive * 100f); + } + } + + public static class HttpClientProgressExtensions + { + public static async Task DownloadFileAsync(this HttpClient client, Uri address, string fileName, Action progress = null, Action completed = null, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + using FileStream destination = File.Create(fileName); + using HttpResponseMessage response = await client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead); + long? contentLength = response.Content.Headers.ContentLength; + using Stream download = await response.Content.ReadAsStreamAsync(); + + if (progress is null || !contentLength.HasValue) + { + await download.CopyToAsync(destination); + if (completed != null) + completed(true); + return; + } + + Progress progressWrapper = new Progress(totalBytes => progress(new HttpClientDownloadProgress(totalBytes, contentLength.Value))); + await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken); + + if (completed != null) + completed(true); + } + catch + { + if (completed != null) + completed(false); + } + } + + private static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (bufferSize < 0) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + if (!source.CanRead) + throw new InvalidOperationException($"'{nameof(source)}' is not readable."); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + if (!destination.CanWrite) + throw new InvalidOperationException($"'{nameof(destination)}' is not writable."); + + byte[] buffer = new byte[bufferSize]; + long totalBytesRead = 0; + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + progress?.Report(totalBytesRead); + } + } + } + + public class DownloaderNameConvertor : IValueConverter + { + public object Convert(object value, Type targetType, + object parameter, CultureInfo culture) + { + return Path.GetFileNameWithoutExtension((string)value); + } + + public object ConvertBack(object value, Type targetType, + object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } + + public class DownloaderSizeConvertor : IValueConverter + { + public object Convert(object value, Type targetType, + object parameter, CultureInfo culture) + { + long? Size = value as long?; + if (Size < 1024) + { + return Size + " B"; + } + + if (Size < (1024 * 1024)) + { + return Math.Round((double)Size / 1024, 0) + " KB"; + } + + return Math.Round((double)Size / 1024 / 1024, 0) + " MB"; + } + + public object ConvertBack(object value, Type targetType, + object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } + + public class DownloaderSpeedConvertor : IValueConverter + { + public object Convert(object value, Type targetType, + object parameter, CultureInfo culture) + { + return ((int)((double)value / 1024)).ToString() + " KB/s"; + } + + public object ConvertBack(object value, Type targetType, + object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } + + public class DownloaderTimeRemainingConvertor : IValueConverter + { + public object Convert(object value, Type targetType, + object parameter, CultureInfo culture) + { + TimeSpan TimeLeft = (TimeSpan)value; + if (TimeLeft == Timeout.InfiniteTimeSpan) + { + return ""; + } + + return TimeLeft.ToString(@"h\:mm\:ss"); + } + + public object ConvertBack(object value, Type targetType, + object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} diff --git a/ViewModels/DumpRomTargetSelectionViewModel.cs b/WPinternals/ViewModels/DumpRomTargetSelectionViewModel.cs similarity index 96% rename from ViewModels/DumpRomTargetSelectionViewModel.cs rename to WPinternals/ViewModels/DumpRomTargetSelectionViewModel.cs index 6a8d960..aa0a0c5 100644 --- a/ViewModels/DumpRomTargetSelectionViewModel.cs +++ b/WPinternals/ViewModels/DumpRomTargetSelectionViewModel.cs @@ -1,228 +1,228 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - internal class DumpRomTargetSelectionViewModel : ContextViewModel - { - private readonly Action DumpCallback; - internal Action SwitchToUnlockBoot; - internal Action SwitchToUnlockRoot; - internal Action SwitchToFlashRom; - - internal DumpRomTargetSelectionViewModel(Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToFlashRom, Action DumpCallback) - : base() - { - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.SwitchToUnlockRoot = SwitchToUnlockRoot; - this.SwitchToFlashRom = SwitchToFlashRom; - this.DumpCallback = DumpCallback; - - new Thread(() => EvaluateViewState()).Start(); - } - - private string _FFUPath; - public string FFUPath - { - get - { - return _FFUPath; - } - set - { - if (value != _FFUPath) - { - _FFUPath = value; - OnPropertyChanged(nameof(FFUPath)); - } - } - } - - private string _EFIESPPath; - public string EFIESPPath - { - get - { - return _EFIESPPath; - } - set - { - if (value != _EFIESPPath) - { - _EFIESPPath = value; - OnPropertyChanged(nameof(EFIESPPath)); - } - } - } - - private bool _CompressEFIESP; - public bool CompressEFIESP - { - get - { - return _CompressEFIESP; - } - set - { - if (value != _CompressEFIESP) - { - _CompressEFIESP = value; - OnPropertyChanged(nameof(CompressEFIESP)); - } - } - } - - private string _MainOSPath; - public string MainOSPath - { - get - { - return _MainOSPath; - } - set - { - if (value != _MainOSPath) - { - _MainOSPath = value; - OnPropertyChanged(nameof(MainOSPath)); - } - } - } - - private bool _CompressMainOS; - public bool CompressMainOS - { - get - { - return _CompressMainOS; - } - set - { - if (value != _CompressMainOS) - { - _CompressMainOS = value; - OnPropertyChanged(nameof(CompressMainOS)); - } - } - } - - private string _DataPath; - public string DataPath - { - get - { - return _DataPath; - } - set - { - if (value != _DataPath) - { - _DataPath = value; - OnPropertyChanged(nameof(DataPath)); - } - } - } - - private bool _CompressData = true; - public bool CompressData - { - get - { - return _CompressData; - } - set - { - if (value != _CompressData) - { - _CompressData = value; - OnPropertyChanged(nameof(CompressData)); - } - } - } - - private bool _IsPhoneDisconnected; - public bool IsPhoneDisconnected - { - get - { - return _IsPhoneDisconnected; - } - set - { - if (value != _IsPhoneDisconnected) - { - _IsPhoneDisconnected = value; - OnPropertyChanged(nameof(IsPhoneDisconnected)); - } - } - } - - private bool _IsPhoneInMassStorage; - public bool IsPhoneInMassStorage - { - get - { - return _IsPhoneInMassStorage; - } - set - { - if (value != _IsPhoneInMassStorage) - { - _IsPhoneInMassStorage = value; - OnPropertyChanged(nameof(IsPhoneInMassStorage)); - } - } - } - - private bool _IsPhoneInOtherMode; - public bool IsPhoneInOtherMode - { - get - { - return _IsPhoneInOtherMode; - } - set - { - if (value != _IsPhoneInOtherMode) - { - _IsPhoneInOtherMode = value; - OnPropertyChanged(nameof(IsPhoneInOtherMode)); - } - } - } - - private DelegateCommand _DumpCommand; - public DelegateCommand DumpCommand - { - get - { - return _DumpCommand ??= new DelegateCommand(() => DumpCallback(FFUPath, EFIESPPath, CompressEFIESP, MainOSPath, CompressMainOS, DataPath, CompressData), () => (FFUPath != null) && ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null))); - } - } - - internal override void EvaluateViewState() - { - DumpCommand.RaiseCanExecuteChanged(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + internal class DumpRomTargetSelectionViewModel : ContextViewModel + { + private readonly Action DumpCallback; + internal Action SwitchToUnlockBoot; + internal Action SwitchToUnlockRoot; + internal Action SwitchToFlashRom; + + internal DumpRomTargetSelectionViewModel(Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToFlashRom, Action DumpCallback) + : base() + { + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.SwitchToUnlockRoot = SwitchToUnlockRoot; + this.SwitchToFlashRom = SwitchToFlashRom; + this.DumpCallback = DumpCallback; + + new Thread(() => EvaluateViewState()).Start(); + } + + private string _FFUPath; + public string FFUPath + { + get + { + return _FFUPath; + } + set + { + if (value != _FFUPath) + { + _FFUPath = value; + OnPropertyChanged(nameof(FFUPath)); + } + } + } + + private string _EFIESPPath; + public string EFIESPPath + { + get + { + return _EFIESPPath; + } + set + { + if (value != _EFIESPPath) + { + _EFIESPPath = value; + OnPropertyChanged(nameof(EFIESPPath)); + } + } + } + + private bool _CompressEFIESP; + public bool CompressEFIESP + { + get + { + return _CompressEFIESP; + } + set + { + if (value != _CompressEFIESP) + { + _CompressEFIESP = value; + OnPropertyChanged(nameof(CompressEFIESP)); + } + } + } + + private string _MainOSPath; + public string MainOSPath + { + get + { + return _MainOSPath; + } + set + { + if (value != _MainOSPath) + { + _MainOSPath = value; + OnPropertyChanged(nameof(MainOSPath)); + } + } + } + + private bool _CompressMainOS; + public bool CompressMainOS + { + get + { + return _CompressMainOS; + } + set + { + if (value != _CompressMainOS) + { + _CompressMainOS = value; + OnPropertyChanged(nameof(CompressMainOS)); + } + } + } + + private string _DataPath; + public string DataPath + { + get + { + return _DataPath; + } + set + { + if (value != _DataPath) + { + _DataPath = value; + OnPropertyChanged(nameof(DataPath)); + } + } + } + + private bool _CompressData = true; + public bool CompressData + { + get + { + return _CompressData; + } + set + { + if (value != _CompressData) + { + _CompressData = value; + OnPropertyChanged(nameof(CompressData)); + } + } + } + + private bool _IsPhoneDisconnected; + public bool IsPhoneDisconnected + { + get + { + return _IsPhoneDisconnected; + } + set + { + if (value != _IsPhoneDisconnected) + { + _IsPhoneDisconnected = value; + OnPropertyChanged(nameof(IsPhoneDisconnected)); + } + } + } + + private bool _IsPhoneInMassStorage; + public bool IsPhoneInMassStorage + { + get + { + return _IsPhoneInMassStorage; + } + set + { + if (value != _IsPhoneInMassStorage) + { + _IsPhoneInMassStorage = value; + OnPropertyChanged(nameof(IsPhoneInMassStorage)); + } + } + } + + private bool _IsPhoneInOtherMode; + public bool IsPhoneInOtherMode + { + get + { + return _IsPhoneInOtherMode; + } + set + { + if (value != _IsPhoneInOtherMode) + { + _IsPhoneInOtherMode = value; + OnPropertyChanged(nameof(IsPhoneInOtherMode)); + } + } + } + + private DelegateCommand _DumpCommand; + public DelegateCommand DumpCommand + { + get + { + return _DumpCommand ??= new DelegateCommand(() => DumpCallback(FFUPath, EFIESPPath, CompressEFIESP, MainOSPath, CompressMainOS, DataPath, CompressData), () => (FFUPath != null) && ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null))); + } + } + + internal override void EvaluateViewState() + { + DumpCommand.RaiseCanExecuteChanged(); + } + } +} diff --git a/ViewModels/DumpRomViewModel.cs b/WPinternals/ViewModels/DumpRomViewModel.cs similarity index 97% rename from ViewModels/DumpRomViewModel.cs rename to WPinternals/ViewModels/DumpRomViewModel.cs index d4319bd..7ba6c40 100644 --- a/ViewModels/DumpRomViewModel.cs +++ b/WPinternals/ViewModels/DumpRomViewModel.cs @@ -1,173 +1,173 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Linq; -using System.Threading; - -namespace WPinternals -{ - internal class DumpRomViewModel : ContextViewModel - { - private readonly Action SwitchToUnlockBoot; - private readonly Action SwitchToUnlockRoot; - private readonly Action SwitchToFlashRom; - - internal DumpRomViewModel(Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToFlashRom) - : base() - { - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.SwitchToUnlockRoot = SwitchToUnlockRoot; - this.SwitchToFlashRom = SwitchToFlashRom; - } - - internal override void EvaluateViewState() - { - if (!IsActive) - { - return; - } - - if (SubContextViewModel == null) - { - ActivateSubContext(new DumpRomTargetSelectionViewModel(SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToFlashRom, DoDumpRom)); - } - } - - internal void DoDumpRom(string FFUPath, string EFIESPPath, bool CompressEFIESP, string MainOSPath, bool CompressMainOS, string DataPath, bool CompressData) - { - new Thread(() => - { - bool Result = true; - - ActivateSubContext(new BusyViewModel("Initializing ROM dump...")); - - ulong TotalSizeSectors = 0; - int PartitionCount = 0; - Partition Partition; - FFU FFU = null; - try - { - FFU = new FFU(FFUPath); - - if (EFIESPPath != null) - { - Partition = FFU.GPT.Partitions.First(p => p.Name == "EFIESP"); - TotalSizeSectors += Partition.SizeInSectors; - PartitionCount++; - } - - if (MainOSPath != null) - { - Partition = FFU.GPT.Partitions.First(p => p.Name == "MainOS"); - TotalSizeSectors += Partition.SizeInSectors; - PartitionCount++; - } - - if (DataPath != null) - { - Partition = FFU.GPT.Partitions.First(p => p.Name == "Data"); - TotalSizeSectors += Partition.SizeInSectors; - PartitionCount++; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - // We are on a worker thread! - // So we must pass the SynchronizationContext of the UI thread - BusyViewModel Busy = new("Dumping ROM...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); - ProgressUpdater Updater = Busy.ProgressUpdater; - ActivateSubContext(Busy); - - int i = 0; - if (Result) - { - try - { - if (EFIESPPath != null) - { - i++; - Busy.Message = "Dumping partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - FFU.WritePartition("EFIESP", EFIESPPath, Updater, CompressEFIESP); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (MainOSPath != null) - { - i++; - Busy.Message = "Dumping partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - FFU.WritePartition("MainOS", MainOSPath, Updater, CompressMainOS); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (DataPath != null) - { - i++; - Busy.Message = "Dumping partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - FFU.WritePartition("Data", DataPath, Updater, CompressData); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to dump ROM partitions!", Restart)); - return; - } - - ActivateSubContext(new MessageViewModel("Successfully dumped ROM partitions!", Restart)); - }).Start(); - } - - internal void Restart() - { - ActivateSubContext(new DumpRomTargetSelectionViewModel(SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToFlashRom, DoDumpRom)); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Threading; + +namespace WPinternals +{ + internal class DumpRomViewModel : ContextViewModel + { + private readonly Action SwitchToUnlockBoot; + private readonly Action SwitchToUnlockRoot; + private readonly Action SwitchToFlashRom; + + internal DumpRomViewModel(Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToFlashRom) + : base() + { + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.SwitchToUnlockRoot = SwitchToUnlockRoot; + this.SwitchToFlashRom = SwitchToFlashRom; + } + + internal override void EvaluateViewState() + { + if (!IsActive) + { + return; + } + + if (SubContextViewModel == null) + { + ActivateSubContext(new DumpRomTargetSelectionViewModel(SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToFlashRom, DoDumpRom)); + } + } + + internal void DoDumpRom(string FFUPath, string EFIESPPath, bool CompressEFIESP, string MainOSPath, bool CompressMainOS, string DataPath, bool CompressData) + { + new Thread(() => + { + bool Result = true; + + ActivateSubContext(new BusyViewModel("Initializing ROM dump...")); + + ulong TotalSizeSectors = 0; + int PartitionCount = 0; + Partition Partition; + FFU FFU = null; + try + { + FFU = new FFU(FFUPath); + + if (EFIESPPath != null) + { + Partition = FFU.GPT.Partitions.First(p => p.Name == "EFIESP"); + TotalSizeSectors += Partition.SizeInSectors; + PartitionCount++; + } + + if (MainOSPath != null) + { + Partition = FFU.GPT.Partitions.First(p => p.Name == "MainOS"); + TotalSizeSectors += Partition.SizeInSectors; + PartitionCount++; + } + + if (DataPath != null) + { + Partition = FFU.GPT.Partitions.First(p => p.Name == "Data"); + TotalSizeSectors += Partition.SizeInSectors; + PartitionCount++; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + // We are on a worker thread! + // So we must pass the SynchronizationContext of the UI thread + BusyViewModel Busy = new("Dumping ROM...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); + ProgressUpdater Updater = Busy.ProgressUpdater; + ActivateSubContext(Busy); + + int i = 0; + if (Result) + { + try + { + if (EFIESPPath != null) + { + i++; + Busy.Message = "Dumping partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + FFU.WritePartition("EFIESP", EFIESPPath, Updater, CompressEFIESP); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (MainOSPath != null) + { + i++; + Busy.Message = "Dumping partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + FFU.WritePartition("MainOS", MainOSPath, Updater, CompressMainOS); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (DataPath != null) + { + i++; + Busy.Message = "Dumping partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + FFU.WritePartition("Data", DataPath, Updater, CompressData); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to dump ROM partitions!", Restart)); + return; + } + + ActivateSubContext(new MessageViewModel("Successfully dumped ROM partitions!", Restart)); + }).Start(); + } + + internal void Restart() + { + ActivateSubContext(new DumpRomTargetSelectionViewModel(SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToFlashRom, DoDumpRom)); + } + } +} diff --git a/ViewModels/GettingStartedViewModel.cs b/WPinternals/ViewModels/GettingStartedViewModel.cs similarity index 97% rename from ViewModels/GettingStartedViewModel.cs rename to WPinternals/ViewModels/GettingStartedViewModel.cs index 2d94676..5843d44 100644 --- a/ViewModels/GettingStartedViewModel.cs +++ b/WPinternals/ViewModels/GettingStartedViewModel.cs @@ -1,47 +1,47 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class GettingStartedViewModel : ContextViewModel - { - internal Action ShowDisclaimer; - internal Action SwitchToUnlockBoot; - internal Action SwitchToUnlockRoot; - internal Action SwitchToBackup; - internal Action SwitchToDumpRom; - internal Action SwitchToFlashRom; - internal Action SwitchToDownload; - - internal GettingStartedViewModel(Action ShowDisclaimer, Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToBackup, Action SwitchToDumpRom, Action SwitchToFlashRom, Action SwitchToDownload) - : base() - { - this.ShowDisclaimer = ShowDisclaimer; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.SwitchToUnlockRoot = SwitchToUnlockRoot; - this.SwitchToBackup = SwitchToBackup; - this.SwitchToFlashRom = SwitchToFlashRom; - this.SwitchToDumpRom = SwitchToDumpRom; - this.SwitchToDownload = SwitchToDownload; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class GettingStartedViewModel : ContextViewModel + { + internal Action ShowDisclaimer; + internal Action SwitchToUnlockBoot; + internal Action SwitchToUnlockRoot; + internal Action SwitchToBackup; + internal Action SwitchToDumpRom; + internal Action SwitchToFlashRom; + internal Action SwitchToDownload; + + internal GettingStartedViewModel(Action ShowDisclaimer, Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToBackup, Action SwitchToDumpRom, Action SwitchToFlashRom, Action SwitchToDownload) + : base() + { + this.ShowDisclaimer = ShowDisclaimer; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.SwitchToUnlockRoot = SwitchToUnlockRoot; + this.SwitchToBackup = SwitchToBackup; + this.SwitchToFlashRom = SwitchToFlashRom; + this.SwitchToDumpRom = SwitchToDumpRom; + this.SwitchToDownload = SwitchToDownload; + } + } +} diff --git a/ViewModels/LumiaFlashRomSourceSelectionViewModel.cs b/WPinternals/ViewModels/LumiaFlashRomSourceSelectionViewModel.cs similarity index 97% rename from ViewModels/LumiaFlashRomSourceSelectionViewModel.cs rename to WPinternals/ViewModels/LumiaFlashRomSourceSelectionViewModel.cs index 27ec86c..d544a28 100644 --- a/ViewModels/LumiaFlashRomSourceSelectionViewModel.cs +++ b/WPinternals/ViewModels/LumiaFlashRomSourceSelectionViewModel.cs @@ -1,279 +1,279 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - internal class LumiaFlashRomSourceSelectionViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - private readonly Action FlashPartitionsCallback; - private readonly Action FlashFFUCallback; - private readonly Action FlashMMOSCallback; - private readonly Action FlashArchiveCallback; - internal Action SwitchToUnlockBoot; - internal Action SwitchToUnlockRoot; - internal Action SwitchToDumpFFU; - internal Action SwitchToBackup; - - internal LumiaFlashRomSourceSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToDumpFFU, Action SwitchToBackup, Action FlashPartitionsCallback, Action FlashArchiveCallback, Action FlashFFUCallback, Action FlashMMOSCallback) - : base() - { - this.PhoneNotifier = PhoneNotifier; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.SwitchToUnlockRoot = SwitchToUnlockRoot; - this.SwitchToDumpFFU = SwitchToDumpFFU; - this.SwitchToBackup = SwitchToBackup; - this.FlashPartitionsCallback = FlashPartitionsCallback; - this.FlashArchiveCallback = FlashArchiveCallback; - this.FlashFFUCallback = FlashFFUCallback; - this.FlashMMOSCallback = FlashMMOSCallback; - - this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - this.PhoneNotifier.DeviceRemoved += DeviceRemoved; - - new Thread(() => EvaluateViewState()).Start(); - } - - private string _EFIESPPath; - public string EFIESPPath - { - get - { - return _EFIESPPath; - } - set - { - if (value != _EFIESPPath) - { - _EFIESPPath = value; - OnPropertyChanged(nameof(EFIESPPath)); - } - } - } - - private string _MainOSPath; - public string MainOSPath - { - get - { - return _MainOSPath; - } - set - { - if (value != _MainOSPath) - { - _MainOSPath = value; - OnPropertyChanged(nameof(MainOSPath)); - } - } - } - - private string _DataPath; - public string DataPath - { - get - { - return _DataPath; - } - set - { - if (value != _DataPath) - { - _DataPath = value; - OnPropertyChanged(nameof(DataPath)); - } - } - } - - private string _ArchivePath; - public string ArchivePath - { - get - { - return _ArchivePath; - } - set - { - if (value != _ArchivePath) - { - _ArchivePath = value; - OnPropertyChanged(nameof(ArchivePath)); - } - } - } - - private string _FFUPath; - public string FFUPath - { - get - { - return _FFUPath; - } - set - { - if (value != _FFUPath) - { - _FFUPath = value; - OnPropertyChanged(nameof(FFUPath)); - } - } - } - - private string _MMOSPath; - public string MMOSPath - { - get - { - return _MMOSPath; - } - set - { - if (value != _MMOSPath) - { - _MMOSPath = value; - OnPropertyChanged(nameof(MMOSPath)); - } - } - } - - private bool _IsPhoneDisconnected; - public bool IsPhoneDisconnected - { - get - { - return _IsPhoneDisconnected; - } - set - { - if (value != _IsPhoneDisconnected) - { - _IsPhoneDisconnected = value; - OnPropertyChanged(nameof(IsPhoneDisconnected)); - } - } - } - - private bool _IsPhoneInFlashMode; - public bool IsPhoneInFlashMode - { - get - { - return _IsPhoneInFlashMode; - } - set - { - if (value != _IsPhoneInFlashMode) - { - _IsPhoneInFlashMode = value; - OnPropertyChanged(nameof(IsPhoneInFlashMode)); - } - } - } - - private bool _IsPhoneInOtherMode; - public bool IsPhoneInOtherMode - { - get - { - return _IsPhoneInOtherMode; - } - set - { - if (value != _IsPhoneInOtherMode) - { - _IsPhoneInOtherMode = value; - OnPropertyChanged(nameof(IsPhoneInOtherMode)); - } - } - } - - private DelegateCommand _FlashPartitionsCommand; - public DelegateCommand FlashPartitionsCommand - { - get - { - return _FlashPartitionsCommand ??= new DelegateCommand(() => FlashPartitionsCallback(EFIESPPath, MainOSPath, DataPath), () => ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null)) && (PhoneNotifier.CurrentInterface != null)); - } - } - - private DelegateCommand _FlashFFUCommand; - public DelegateCommand FlashFFUCommand - { - get - { - return _FlashFFUCommand ??= new DelegateCommand(() => FlashFFUCallback(FFUPath), () => (FFUPath != null) && (PhoneNotifier.CurrentInterface != null)); - } - } - - private DelegateCommand _FlashMMOSCommand; - public DelegateCommand FlashMMOSCommand - { - get - { - return _FlashMMOSCommand ??= new DelegateCommand(() => FlashMMOSCallback(MMOSPath), () => (MMOSPath != null) && (PhoneNotifier.CurrentInterface != null)); - } - } - - private DelegateCommand _FlashArchiveCommand; - public DelegateCommand FlashArchiveCommand - { - get - { - return _FlashArchiveCommand ??= new DelegateCommand(() => FlashArchiveCallback(ArchivePath), () => (ArchivePath != null) && (PhoneNotifier.CurrentInterface != null)); - } - } - - ~LumiaFlashRomSourceSelectionViewModel() - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - new Thread(() => - { - try - { - EvaluateViewState(); - } - catch { } - }).Start(); - } - - private void DeviceRemoved() - { - new Thread(() => EvaluateViewState()).Start(); - } - - internal override void EvaluateViewState() - { - IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; - IsPhoneInFlashMode = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Flash; - IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInFlashMode; - FlashPartitionsCommand.RaiseCanExecuteChanged(); - FlashArchiveCommand.RaiseCanExecuteChanged(); - FlashFFUCommand.RaiseCanExecuteChanged(); - FlashMMOSCommand.RaiseCanExecuteChanged(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + internal class LumiaFlashRomSourceSelectionViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + private readonly Action FlashPartitionsCallback; + private readonly Action FlashFFUCallback; + private readonly Action FlashMMOSCallback; + private readonly Action FlashArchiveCallback; + internal Action SwitchToUnlockBoot; + internal Action SwitchToUnlockRoot; + internal Action SwitchToDumpFFU; + internal Action SwitchToBackup; + + internal LumiaFlashRomSourceSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToDumpFFU, Action SwitchToBackup, Action FlashPartitionsCallback, Action FlashArchiveCallback, Action FlashFFUCallback, Action FlashMMOSCallback) + : base() + { + this.PhoneNotifier = PhoneNotifier; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.SwitchToUnlockRoot = SwitchToUnlockRoot; + this.SwitchToDumpFFU = SwitchToDumpFFU; + this.SwitchToBackup = SwitchToBackup; + this.FlashPartitionsCallback = FlashPartitionsCallback; + this.FlashArchiveCallback = FlashArchiveCallback; + this.FlashFFUCallback = FlashFFUCallback; + this.FlashMMOSCallback = FlashMMOSCallback; + + this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + this.PhoneNotifier.DeviceRemoved += DeviceRemoved; + + new Thread(() => EvaluateViewState()).Start(); + } + + private string _EFIESPPath; + public string EFIESPPath + { + get + { + return _EFIESPPath; + } + set + { + if (value != _EFIESPPath) + { + _EFIESPPath = value; + OnPropertyChanged(nameof(EFIESPPath)); + } + } + } + + private string _MainOSPath; + public string MainOSPath + { + get + { + return _MainOSPath; + } + set + { + if (value != _MainOSPath) + { + _MainOSPath = value; + OnPropertyChanged(nameof(MainOSPath)); + } + } + } + + private string _DataPath; + public string DataPath + { + get + { + return _DataPath; + } + set + { + if (value != _DataPath) + { + _DataPath = value; + OnPropertyChanged(nameof(DataPath)); + } + } + } + + private string _ArchivePath; + public string ArchivePath + { + get + { + return _ArchivePath; + } + set + { + if (value != _ArchivePath) + { + _ArchivePath = value; + OnPropertyChanged(nameof(ArchivePath)); + } + } + } + + private string _FFUPath; + public string FFUPath + { + get + { + return _FFUPath; + } + set + { + if (value != _FFUPath) + { + _FFUPath = value; + OnPropertyChanged(nameof(FFUPath)); + } + } + } + + private string _MMOSPath; + public string MMOSPath + { + get + { + return _MMOSPath; + } + set + { + if (value != _MMOSPath) + { + _MMOSPath = value; + OnPropertyChanged(nameof(MMOSPath)); + } + } + } + + private bool _IsPhoneDisconnected; + public bool IsPhoneDisconnected + { + get + { + return _IsPhoneDisconnected; + } + set + { + if (value != _IsPhoneDisconnected) + { + _IsPhoneDisconnected = value; + OnPropertyChanged(nameof(IsPhoneDisconnected)); + } + } + } + + private bool _IsPhoneInFlashMode; + public bool IsPhoneInFlashMode + { + get + { + return _IsPhoneInFlashMode; + } + set + { + if (value != _IsPhoneInFlashMode) + { + _IsPhoneInFlashMode = value; + OnPropertyChanged(nameof(IsPhoneInFlashMode)); + } + } + } + + private bool _IsPhoneInOtherMode; + public bool IsPhoneInOtherMode + { + get + { + return _IsPhoneInOtherMode; + } + set + { + if (value != _IsPhoneInOtherMode) + { + _IsPhoneInOtherMode = value; + OnPropertyChanged(nameof(IsPhoneInOtherMode)); + } + } + } + + private DelegateCommand _FlashPartitionsCommand; + public DelegateCommand FlashPartitionsCommand + { + get + { + return _FlashPartitionsCommand ??= new DelegateCommand(() => FlashPartitionsCallback(EFIESPPath, MainOSPath, DataPath), () => ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null)) && (PhoneNotifier.CurrentInterface != null)); + } + } + + private DelegateCommand _FlashFFUCommand; + public DelegateCommand FlashFFUCommand + { + get + { + return _FlashFFUCommand ??= new DelegateCommand(() => FlashFFUCallback(FFUPath), () => (FFUPath != null) && (PhoneNotifier.CurrentInterface != null)); + } + } + + private DelegateCommand _FlashMMOSCommand; + public DelegateCommand FlashMMOSCommand + { + get + { + return _FlashMMOSCommand ??= new DelegateCommand(() => FlashMMOSCallback(MMOSPath), () => (MMOSPath != null) && (PhoneNotifier.CurrentInterface != null)); + } + } + + private DelegateCommand _FlashArchiveCommand; + public DelegateCommand FlashArchiveCommand + { + get + { + return _FlashArchiveCommand ??= new DelegateCommand(() => FlashArchiveCallback(ArchivePath), () => (ArchivePath != null) && (PhoneNotifier.CurrentInterface != null)); + } + } + + ~LumiaFlashRomSourceSelectionViewModel() + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + new Thread(() => + { + try + { + EvaluateViewState(); + } + catch { } + }).Start(); + } + + private void DeviceRemoved() + { + new Thread(() => EvaluateViewState()).Start(); + } + + internal override void EvaluateViewState() + { + IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; + IsPhoneInFlashMode = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Flash; + IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInFlashMode; + FlashPartitionsCommand.RaiseCanExecuteChanged(); + FlashArchiveCommand.RaiseCanExecuteChanged(); + FlashFFUCommand.RaiseCanExecuteChanged(); + FlashMMOSCommand.RaiseCanExecuteChanged(); + } + } +} diff --git a/ViewModels/LumiaFlashRomViewModel.cs b/WPinternals/ViewModels/LumiaFlashRomViewModel.cs similarity index 97% rename from ViewModels/LumiaFlashRomViewModel.cs rename to WPinternals/ViewModels/LumiaFlashRomViewModel.cs index faa3f15..e64ae27 100644 --- a/ViewModels/LumiaFlashRomViewModel.cs +++ b/WPinternals/ViewModels/LumiaFlashRomViewModel.cs @@ -1,773 +1,773 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace WPinternals -{ - internal class LumiaFlashRomViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - internal Action SwitchToUnlockBoot; - internal Action SwitchToUnlockRoot; - internal Action SwitchToDumpFFU; - internal Action SwitchToBackup; - private readonly Action Callback; - - internal LumiaFlashRomViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToDumpFFU, Action SwitchToBackup, Action Callback) - : base() - { - IsSwitchingInterface = false; - IsFlashModeOperation = true; - - this.PhoneNotifier = PhoneNotifier; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.SwitchToUnlockRoot = SwitchToUnlockRoot; - this.SwitchToDumpFFU = SwitchToDumpFFU; - this.SwitchToBackup = SwitchToBackup; - this.Callback = Callback; - } - - internal override void EvaluateViewState() - { - if (!IsActive) - { - return; - } - - if (SubContextViewModel == null) - { - ActivateSubContext(new LumiaFlashRomSourceSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToDumpFFU, SwitchToBackup, FlashPartitions, FlashArchive, FlashFFU, FlashMMOS)); - } - } - - // Called from an event-handler. So, "async void" is valid here. - internal async void FlashPartitions(string EFIESPPath, string MainOSPath, string DataPath) - { - IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - if (((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(ExtendedInfo: false).FlashAppProtocolVersionMajor < 2) - { - FlashPartitionsTask(EFIESPPath, MainOSPath, DataPath); - } - else - { - await Task.Run(async () => await LumiaV2UnlockBootViewModel.LumiaV2FlashPartitions(PhoneNotifier, EFIESPPath, MainOSPath, DataPath, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure)); - } - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal void FlashPartitionsTask(string EFIESPPath, string MainOSPath, string DataPath) - { - new Thread(() => - { - bool Result = true; - - ActivateSubContext(new BusyViewModel("Initializing flash...")); - - NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; - - GPT GPT = Phone.ReadGPT(); - - ulong TotalSizeSectors = 0; - int PartitionCount = 0; - ulong MainOSOldSectorCount = 0; - ulong MainOSNewSectorCount = 0; - ulong DataOldSectorCount = 0; - ulong DataNewSectorCount = 0; - ulong FirstMainOSSector = 0; - - try - { - if (EFIESPPath != null) - { - using Stream Stream = new DecompressedStream(File.Open(EFIESPPath, FileMode.Open)); - ulong StreamLengthInSectors = (ulong)Stream.Length / 0x200; - TotalSizeSectors += StreamLengthInSectors; - PartitionCount++; - Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, "EFIESP", StringComparison.CurrentCultureIgnoreCase)); - if (StreamLengthInSectors > Partition.SizeInSectors) - { - LogFile.Log("Flash failed! Size of partition 'EFIESP' is too big."); - ExitFailure("Flash failed!", "Size of partition 'EFIESP' is too big."); - return; - } - } - - if (MainOSPath != null) - { - using Stream Stream = new DecompressedStream(File.Open(MainOSPath, FileMode.Open)); - ulong StreamLengthInSectors = (ulong)Stream.Length / 0x200; - TotalSizeSectors += StreamLengthInSectors; - PartitionCount++; - Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); - MainOSOldSectorCount = Partition.SizeInSectors; - MainOSNewSectorCount = StreamLengthInSectors; - FirstMainOSSector = Partition.FirstSector; - } - - if (DataPath != null) - { - using Stream Stream = new DecompressedStream(File.Open(DataPath, FileMode.Open)); - ulong StreamLengthInSectors = (ulong)Stream.Length / 0x200; - TotalSizeSectors += StreamLengthInSectors; - PartitionCount++; - Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); - DataOldSectorCount = Partition.SizeInSectors; - DataNewSectorCount = StreamLengthInSectors; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) - { - if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) - { - UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; - if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) - { - // MainOS and Data partitions need to be re-aligned! - Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); - Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); - MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; - DataPartition.FirstSector = MainOSPartition.LastSector + 1; - DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; - Phone.WriteGPT(GPT); - } - else - { - LogFile.Log("Flash failed! Size of partitions 'MainOS' and 'Data' together are too big."); - ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); - return; - } - } - } - else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'MainOS' is too big."); - ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); - return; - } - else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'Data' is too big."); - ExitFailure("Flash failed!", "Size of partition 'Data' together is too big."); - return; - } - - BusyViewModel Busy = new("Flashing...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); - ProgressUpdater Updater = Busy.ProgressUpdater; - ActivateSubContext(Busy); - - int i = 0; - if (Result) - { - try - { - if (EFIESPPath != null) - { - i++; - Busy.Message = "Flashing partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.FlashRawPartition(EFIESPPath, "EFIESP", Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (MainOSPath != null) - { - i++; - Busy.Message = "Flashing partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.FlashRawPartition(MainOSPath, "MainOS", Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (DataPath != null) - { - i++; - Busy.Message = "Flashing partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.FlashRawPartition(DataPath, "Data", Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (!Result) - { - ExitFailure("Flash failed!", null); - return; - } - - ExitSuccess("Flash successful! Make sure you disable Windows Update on the phone!", null); - }).Start(); - } - - // Called from an event-handler. So, "async void" is valid here. - internal async void FlashArchive(string ArchivePath) - { - IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - if (((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(ExtendedInfo: false).FlashAppProtocolVersionMajor < 2) - { - FlashArchiveTask(ArchivePath); - } - else - { - await Task.Run(async () => await LumiaV2UnlockBootViewModel.LumiaV2FlashArchive(PhoneNotifier, ArchivePath, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure)); - } - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal void FlashArchiveTask(string ArchivePath) - { - new Thread(() => - { - ActivateSubContext(new BusyViewModel("Initializing flash...")); - - NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; - - ulong TotalSizeSectors = 0; - int PartitionCount = 0; - ulong MainOSOldSectorCount = 0; - ulong MainOSNewSectorCount = 0; - ulong DataOldSectorCount = 0; - ulong DataNewSectorCount = 0; - ulong FirstMainOSSector = 0; - bool GPTChanged = false; - - try - { - GPT GPT = Phone.ReadGPT(); - - using FileStream FileStream = new(ArchivePath, FileMode.Open); - using ZipArchive Archive = new(FileStream, ZipArchiveMode.Read); - foreach (ZipArchiveEntry Entry in Archive.Entries) - { - // Determine if there is a partition layout present - ZipArchiveEntry PartitionEntry = Archive.GetEntry("Partitions.xml"); - if (PartitionEntry == null) - { - GPT.MergePartitions(null, false, Archive); - GPTChanged |= GPT.HasChanged; - } - else - { - using Stream ZipStream = PartitionEntry.Open(); - using StreamReader ZipReader = new(ZipStream); - string PartitionXml = ZipReader.ReadToEnd(); - GPT.MergePartitions(PartitionXml, false, Archive); - GPTChanged |= GPT.HasChanged; - } - - // First determine if we need a new GPT! - if (!Entry.FullName.Contains("/")) // No subfolders - { - string PartitionName = Path.GetFileNameWithoutExtension(Entry.Name); - int P = PartitionName.IndexOf('.'); - if (P >= 0) - { - PartitionName = PartitionName.Substring(0, P); // Example: Data.bin.gz -> Data - } - - Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); - if (Partition != null) - { - DecompressedStream DecompressedStream = new(Entry.Open()); - ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; - try - { - StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; - } - catch { } - - TotalSizeSectors += StreamLengthInSectors; - PartitionCount++; - - if (string.Equals(PartitionName, "MainOS", StringComparison.CurrentCultureIgnoreCase)) - { - MainOSOldSectorCount = Partition.SizeInSectors; - MainOSNewSectorCount = StreamLengthInSectors; - FirstMainOSSector = Partition.FirstSector; - } - else if (string.Equals(PartitionName, "Data", StringComparison.CurrentCultureIgnoreCase)) - { - DataOldSectorCount = Partition.SizeInSectors; - DataNewSectorCount = StreamLengthInSectors; - } - else if (StreamLengthInSectors > Partition.SizeInSectors) - { - LogFile.Log("Flash failed! Size of partition '" + PartitionName + "' is too big."); - ExitFailure("Flash failed!", "Size of partition '" + PartitionName + "' is too big."); - return; - } - } - } - } - - if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) - { - if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) - { - UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; - if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) - { - // MainOS and Data partitions need to be re-aligned! - Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); - Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); - MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; - DataPartition.FirstSector = MainOSPartition.LastSector + 1; - DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; - - GPTChanged = true; - } - else - { - LogFile.Log("Flash failed! Size of partitions 'MainOS' and 'Data' together are too big."); - ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); - return; - } - } - } - else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'MainOS' is too big."); - ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); - return; - } - else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'Data' is too big."); - ExitFailure("Flash failed!", "Size of partition 'Data' is too big."); - return; - } - - if (GPTChanged) - { - Phone.WriteGPT(GPT); - } - - if (PartitionCount > 0) - { - BusyViewModel Busy = new("Flashing...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); - ProgressUpdater Updater = Busy.ProgressUpdater; - ActivateSubContext(Busy); - - int i = 0; - - foreach (ZipArchiveEntry Entry in Archive.Entries) - { - // "MainOS.bin.gz" => "MainOS" - string PartitionName = Entry.Name; - int Pos = PartitionName.IndexOf('.'); - if (Pos >= 0) - { - PartitionName = PartitionName.Substring(0, Pos); - } - - Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); - if (Partition != null) - { - Stream DecompressedStream = new DecompressedStream(Entry.Open()); - ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; - try - { - StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; - } - catch { } - - if (StreamLengthInSectors <= Partition.SizeInSectors) - { - i++; - Busy.Message = "Flashing partition " + Partition.Name + " (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.FlashRawPartition(DecompressedStream, Partition.Name, Updater); - } - DecompressedStream.Close(); - } - } - } - else - { - LogFile.Log("Flash failed! No valid partitions found in the archive."); - ExitFailure("Flash failed!", "No valid partitions found in the archive"); - return; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - if (Ex is WPinternalsException) - { - ExitFailure("Flash failed!", ((WPinternalsException)Ex).SubMessage); - } - else - { - ExitFailure("Flash failed!", null); - } - - return; - } - - ExitSuccess("Flash successful! Make sure you disable Windows Update on the phone!", null); - }).Start(); - } - - // Called from an event-handler. So, "async void" is valid here. - internal async void FlashFFU(string FFUPath) - { - IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - FlashFFUTask(FFUPath); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal void FlashFFUTask(string FFUPath) - { - new Thread(() => - { - bool Result = true; - - NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; - PhoneInfo Info = Phone.ReadPhoneInfo(false); - - #region Remove bootloader changes - - // If necessary remove bootloader changes - // In case the NV vars were redirected, and a stock FFU is flashed, then the IsFlashing flag will be cleared in the redirected NV vars - // And after a reboot the original NV vars are active again, but the IsFlashing flag is still set from when the bootloader was unlocked - // So we will first restore the GPT, so the original vars are active again. - // Then IsFlashing is true and the phone boots forcibly to FlashApp again. - // Then we start normal FFU flasing and at the end the IsFlashing flag is cleared in the original vars. - - if (Info.FlashAppProtocolVersionMajor >= 2) - { - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Phone, 0x20000); // TODO: Get proper profile FFU and get ChunkSizeInBytes - GPT GPT = new(GPTChunk); - FlashPart Part; - List FlashParts = new(); - - Partition NvBackupPartition = GPT.GetPartition("BACKUP_BS_NV"); - if (NvBackupPartition != null) - { - // This must be a left over of a half unlocked bootloader - Partition NvPartition = GPT.GetPartition("UEFI_BS_NV"); - NvBackupPartition.Name = "UEFI_BS_NV"; - NvBackupPartition.PartitionGuid = NvPartition.PartitionGuid; - NvBackupPartition.PartitionTypeGuid = NvPartition.PartitionTypeGuid; - GPT.Partitions.Remove(NvPartition); - - GPT.Rebuild(); - Part = new FlashPart - { - StartSector = 0, - Stream = new MemoryStream(GPTChunk) - }; - FlashParts.Add(Part); - } - - bool ClearFlashingStatus = true; - - // We should only clear NV if there was no backup NV to be restored and the current NV contains the SB unlock. - if ((NvBackupPartition == null) && !Info.UefiSecureBootEnabled) - { - // ClearNV - Part = new FlashPart(); - Partition Target = GPT.GetPartition("UEFI_BS_NV"); - Part.StartSector = (UInt32)Target.FirstSector; - Part.Stream = new MemoryStream(new byte[0x40000]); - FlashParts.Add(Part); - - ClearFlashingStatus = false; - } - - if (FlashParts.Count > 0) - { - ActivateSubContext(new BusyViewModel("Restoring bootloader...")); - WPinternalsStatus LastStatus = WPinternalsStatus.Undefined; - LumiaV2UnlockBootViewModel.LumiaV2CustomFlash(PhoneNotifier, FFUPath, false, false, FlashParts, true, ClearFlashingStatusAtEnd: ClearFlashingStatus, - SetWorkingStatus: (m, s, v, a, st) => - { - if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) - { - SetWorkingStatus(m, s, v, a, st); - } - else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) - { - SetWorkingStatus("Restoring bootloader...", null, null, Status: WPinternalsStatus.Flashing); - } - - LastStatus = st; - }, - UpdateWorkingStatus: (m, s, v, st) => - { - if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) - { - UpdateWorkingStatus(m, s, v, st); - } - else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) - { - SetWorkingStatus("Restoring bootloader...", null, null, Status: WPinternalsStatus.Flashing); - } - - LastStatus = st; - } - ).Wait(); - - if ((PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Flash)) - { - PhoneNotifier.WaitForArrival().Wait(); - } - - if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) - { - ((NokiaFlashModel)PhoneNotifier.CurrentModel).SwitchToFlashAppContext(); - } - } - } - - #endregion - - Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; - - ActivateSubContext(new BusyViewModel("Initializing flash...")); - - string ErrorSubMessage = null; - - try - { - FFU FFU = new(FFUPath); - BusyViewModel Busy = new("Flashing original FFU...", MaxProgressValue: FFU.TotalChunkCount, UIContext: UIContext); - ActivateSubContext(Busy); - byte Options = 0; - if (!Info.IsBootloaderSecure) - { - Options = (byte)((FlashOptions)Options | FlashOptions.SkipSignatureCheck); - } - - Phone.FlashFFU(FFU, Busy.ProgressUpdater, true, Options); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - if (Ex is WPinternalsException) - { - ErrorSubMessage = ((WPinternalsException)Ex).SubMessage; - } - - Result = false; - } - - if (!Result) - { - ExitFailure("Flash failed!", ErrorSubMessage); - return; - } - - ExitSuccess("Flash successful!", null); - }).Start(); - } - - // Called from an event-handler. So, "async void" is valid here. - internal async void FlashMMOS(string MMOSPath) - { - IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - FlashMMOSTask(MMOSPath); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal void FlashMMOSTask(string MMOSPath) - { - NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; - if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) - { - Phone.SwitchToFlashAppContext(); - } - - new Thread(() => - { - bool Result = true; - - ActivateSubContext(new BusyViewModel("Initializing flash...")); - - string ErrorSubMessage = null; - - try - { - FileInfo info = new(MMOSPath); - uint length = uint.Parse(info.Length.ToString()); - const int maximumbuffersize = 0x00240000; - uint totalcounts = (uint)Math.Truncate((decimal)length / maximumbuffersize); - BusyViewModel Busy = new("Flashing Test Mode package...", MaxProgressValue: totalcounts, UIContext: UIContext); - ActivateSubContext(Busy); - - Phone.FlashMMOS(MMOSPath, Busy.ProgressUpdater); - - ActivateSubContext(new BusyViewModel("And now booting phone to MMOS...", "If the phone stays on the lightning cog screen for a while, you may need to unplug and replug the phone to continue the boot process.")); - - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - if (Ex is WPinternalsException) - { - ErrorSubMessage = ((WPinternalsException)Ex).SubMessage; - } - - Result = false; - } - - if (!Result) - { - ExitFailure("Flash failed!", ErrorSubMessage); - return; - } - }).Start(); - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - - if (Args.NewInterface != PhoneInterfaces.Lumia_Label) - { - ExitFailure("Flash failed!", "Phone unexpectedly switched mode while booting MMOS image."); - return; - } - else - { - ExitSuccess("Flash successful!", null); - return; - } - } - - // Called from an event-handler. So, "async void" is valid here. - internal async void Exit() - { - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Normal, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - IsSwitchingInterface = false; - Callback(); - ActivateSubContext(null); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, () => - { - IsSwitchingInterface = false; - Callback(); - ActivateSubContext(null); - })); - } - } - - internal void ExitSuccess(string Message, string SubMessage) - { - MessageViewModel SuccessMessageViewModel = new(Message, () => - { - // No need to call Exit() to go to normal mode, because it already switches to normal mode automatically. - IsSwitchingInterface = false; // From here on a device will be forced to Flash mode again on this screen which is meant for flashing - Callback(); - ActivateSubContext(null); - }); - SuccessMessageViewModel.SubMessage = SubMessage; - ActivateSubContext(SuccessMessageViewModel); - } - - internal void ExitFailure(string Message, string SubMessage) - { - MessageViewModel ErrorMessageViewModel = new(Message, () => - { - IsSwitchingInterface = false; - Callback(); - ActivateSubContext(null); - }); - ErrorMessageViewModel.SubMessage = SubMessage; - ActivateSubContext(ErrorMessageViewModel); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace WPinternals +{ + internal class LumiaFlashRomViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + internal Action SwitchToUnlockBoot; + internal Action SwitchToUnlockRoot; + internal Action SwitchToDumpFFU; + internal Action SwitchToBackup; + private readonly Action Callback; + + internal LumiaFlashRomViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToUnlockRoot, Action SwitchToDumpFFU, Action SwitchToBackup, Action Callback) + : base() + { + IsSwitchingInterface = false; + IsFlashModeOperation = true; + + this.PhoneNotifier = PhoneNotifier; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.SwitchToUnlockRoot = SwitchToUnlockRoot; + this.SwitchToDumpFFU = SwitchToDumpFFU; + this.SwitchToBackup = SwitchToBackup; + this.Callback = Callback; + } + + internal override void EvaluateViewState() + { + if (!IsActive) + { + return; + } + + if (SubContextViewModel == null) + { + ActivateSubContext(new LumiaFlashRomSourceSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToDumpFFU, SwitchToBackup, FlashPartitions, FlashArchive, FlashFFU, FlashMMOS)); + } + } + + // Called from an event-handler. So, "async void" is valid here. + internal async void FlashPartitions(string EFIESPPath, string MainOSPath, string DataPath) + { + IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + if (((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(ExtendedInfo: false).FlashAppProtocolVersionMajor < 2) + { + FlashPartitionsTask(EFIESPPath, MainOSPath, DataPath); + } + else + { + await Task.Run(async () => await LumiaV2UnlockBootViewModel.LumiaV2FlashPartitions(PhoneNotifier, EFIESPPath, MainOSPath, DataPath, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure)); + } + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal void FlashPartitionsTask(string EFIESPPath, string MainOSPath, string DataPath) + { + new Thread(() => + { + bool Result = true; + + ActivateSubContext(new BusyViewModel("Initializing flash...")); + + NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; + + GPT GPT = Phone.ReadGPT(); + + ulong TotalSizeSectors = 0; + int PartitionCount = 0; + ulong MainOSOldSectorCount = 0; + ulong MainOSNewSectorCount = 0; + ulong DataOldSectorCount = 0; + ulong DataNewSectorCount = 0; + ulong FirstMainOSSector = 0; + + try + { + if (EFIESPPath != null) + { + using Stream Stream = new DecompressedStream(File.Open(EFIESPPath, FileMode.Open)); + ulong StreamLengthInSectors = (ulong)Stream.Length / 0x200; + TotalSizeSectors += StreamLengthInSectors; + PartitionCount++; + Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, "EFIESP", StringComparison.CurrentCultureIgnoreCase)); + if (StreamLengthInSectors > Partition.SizeInSectors) + { + LogFile.Log("Flash failed! Size of partition 'EFIESP' is too big."); + ExitFailure("Flash failed!", "Size of partition 'EFIESP' is too big."); + return; + } + } + + if (MainOSPath != null) + { + using Stream Stream = new DecompressedStream(File.Open(MainOSPath, FileMode.Open)); + ulong StreamLengthInSectors = (ulong)Stream.Length / 0x200; + TotalSizeSectors += StreamLengthInSectors; + PartitionCount++; + Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); + MainOSOldSectorCount = Partition.SizeInSectors; + MainOSNewSectorCount = StreamLengthInSectors; + FirstMainOSSector = Partition.FirstSector; + } + + if (DataPath != null) + { + using Stream Stream = new DecompressedStream(File.Open(DataPath, FileMode.Open)); + ulong StreamLengthInSectors = (ulong)Stream.Length / 0x200; + TotalSizeSectors += StreamLengthInSectors; + PartitionCount++; + Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); + DataOldSectorCount = Partition.SizeInSectors; + DataNewSectorCount = StreamLengthInSectors; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) + { + if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) + { + UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; + if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) + { + // MainOS and Data partitions need to be re-aligned! + Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); + Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); + MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; + DataPartition.FirstSector = MainOSPartition.LastSector + 1; + DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; + Phone.WriteGPT(GPT); + } + else + { + LogFile.Log("Flash failed! Size of partitions 'MainOS' and 'Data' together are too big."); + ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); + return; + } + } + } + else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'MainOS' is too big."); + ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); + return; + } + else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'Data' is too big."); + ExitFailure("Flash failed!", "Size of partition 'Data' together is too big."); + return; + } + + BusyViewModel Busy = new("Flashing...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); + ProgressUpdater Updater = Busy.ProgressUpdater; + ActivateSubContext(Busy); + + int i = 0; + if (Result) + { + try + { + if (EFIESPPath != null) + { + i++; + Busy.Message = "Flashing partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.FlashRawPartition(EFIESPPath, "EFIESP", Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (MainOSPath != null) + { + i++; + Busy.Message = "Flashing partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.FlashRawPartition(MainOSPath, "MainOS", Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (DataPath != null) + { + i++; + Busy.Message = "Flashing partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.FlashRawPartition(DataPath, "Data", Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (!Result) + { + ExitFailure("Flash failed!", null); + return; + } + + ExitSuccess("Flash successful! Make sure you disable Windows Update on the phone!", null); + }).Start(); + } + + // Called from an event-handler. So, "async void" is valid here. + internal async void FlashArchive(string ArchivePath) + { + IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + if (((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(ExtendedInfo: false).FlashAppProtocolVersionMajor < 2) + { + FlashArchiveTask(ArchivePath); + } + else + { + await Task.Run(async () => await LumiaV2UnlockBootViewModel.LumiaV2FlashArchive(PhoneNotifier, ArchivePath, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure)); + } + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal void FlashArchiveTask(string ArchivePath) + { + new Thread(() => + { + ActivateSubContext(new BusyViewModel("Initializing flash...")); + + NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; + + ulong TotalSizeSectors = 0; + int PartitionCount = 0; + ulong MainOSOldSectorCount = 0; + ulong MainOSNewSectorCount = 0; + ulong DataOldSectorCount = 0; + ulong DataNewSectorCount = 0; + ulong FirstMainOSSector = 0; + bool GPTChanged = false; + + try + { + GPT GPT = Phone.ReadGPT(); + + using FileStream FileStream = new(ArchivePath, FileMode.Open); + using ZipArchive Archive = new(FileStream, ZipArchiveMode.Read); + foreach (ZipArchiveEntry Entry in Archive.Entries) + { + // Determine if there is a partition layout present + ZipArchiveEntry PartitionEntry = Archive.GetEntry("Partitions.xml"); + if (PartitionEntry == null) + { + GPT.MergePartitions(null, false, Archive); + GPTChanged |= GPT.HasChanged; + } + else + { + using Stream ZipStream = PartitionEntry.Open(); + using StreamReader ZipReader = new(ZipStream); + string PartitionXml = ZipReader.ReadToEnd(); + GPT.MergePartitions(PartitionXml, false, Archive); + GPTChanged |= GPT.HasChanged; + } + + // First determine if we need a new GPT! + if (!Entry.FullName.Contains("/")) // No subfolders + { + string PartitionName = Path.GetFileNameWithoutExtension(Entry.Name); + int P = PartitionName.IndexOf('.'); + if (P >= 0) + { + PartitionName = PartitionName.Substring(0, P); // Example: Data.bin.gz -> Data + } + + Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); + if (Partition != null) + { + DecompressedStream DecompressedStream = new(Entry.Open()); + ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; + try + { + StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; + } + catch { } + + TotalSizeSectors += StreamLengthInSectors; + PartitionCount++; + + if (string.Equals(PartitionName, "MainOS", StringComparison.CurrentCultureIgnoreCase)) + { + MainOSOldSectorCount = Partition.SizeInSectors; + MainOSNewSectorCount = StreamLengthInSectors; + FirstMainOSSector = Partition.FirstSector; + } + else if (string.Equals(PartitionName, "Data", StringComparison.CurrentCultureIgnoreCase)) + { + DataOldSectorCount = Partition.SizeInSectors; + DataNewSectorCount = StreamLengthInSectors; + } + else if (StreamLengthInSectors > Partition.SizeInSectors) + { + LogFile.Log("Flash failed! Size of partition '" + PartitionName + "' is too big."); + ExitFailure("Flash failed!", "Size of partition '" + PartitionName + "' is too big."); + return; + } + } + } + } + + if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) + { + if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) + { + UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; + if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) + { + // MainOS and Data partitions need to be re-aligned! + Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); + Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); + MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; + DataPartition.FirstSector = MainOSPartition.LastSector + 1; + DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; + + GPTChanged = true; + } + else + { + LogFile.Log("Flash failed! Size of partitions 'MainOS' and 'Data' together are too big."); + ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); + return; + } + } + } + else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'MainOS' is too big."); + ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); + return; + } + else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'Data' is too big."); + ExitFailure("Flash failed!", "Size of partition 'Data' is too big."); + return; + } + + if (GPTChanged) + { + Phone.WriteGPT(GPT); + } + + if (PartitionCount > 0) + { + BusyViewModel Busy = new("Flashing...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); + ProgressUpdater Updater = Busy.ProgressUpdater; + ActivateSubContext(Busy); + + int i = 0; + + foreach (ZipArchiveEntry Entry in Archive.Entries) + { + // "MainOS.bin.gz" => "MainOS" + string PartitionName = Entry.Name; + int Pos = PartitionName.IndexOf('.'); + if (Pos >= 0) + { + PartitionName = PartitionName.Substring(0, Pos); + } + + Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); + if (Partition != null) + { + Stream DecompressedStream = new DecompressedStream(Entry.Open()); + ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; + try + { + StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; + } + catch { } + + if (StreamLengthInSectors <= Partition.SizeInSectors) + { + i++; + Busy.Message = "Flashing partition " + Partition.Name + " (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.FlashRawPartition(DecompressedStream, Partition.Name, Updater); + } + DecompressedStream.Close(); + } + } + } + else + { + LogFile.Log("Flash failed! No valid partitions found in the archive."); + ExitFailure("Flash failed!", "No valid partitions found in the archive"); + return; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + if (Ex is WPinternalsException) + { + ExitFailure("Flash failed!", ((WPinternalsException)Ex).SubMessage); + } + else + { + ExitFailure("Flash failed!", null); + } + + return; + } + + ExitSuccess("Flash successful! Make sure you disable Windows Update on the phone!", null); + }).Start(); + } + + // Called from an event-handler. So, "async void" is valid here. + internal async void FlashFFU(string FFUPath) + { + IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + FlashFFUTask(FFUPath); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal void FlashFFUTask(string FFUPath) + { + new Thread(() => + { + bool Result = true; + + NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; + PhoneInfo Info = Phone.ReadPhoneInfo(false); + + #region Remove bootloader changes + + // If necessary remove bootloader changes + // In case the NV vars were redirected, and a stock FFU is flashed, then the IsFlashing flag will be cleared in the redirected NV vars + // And after a reboot the original NV vars are active again, but the IsFlashing flag is still set from when the bootloader was unlocked + // So we will first restore the GPT, so the original vars are active again. + // Then IsFlashing is true and the phone boots forcibly to FlashApp again. + // Then we start normal FFU flasing and at the end the IsFlashing flag is cleared in the original vars. + + if (Info.FlashAppProtocolVersionMajor >= 2) + { + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Phone, 0x20000); // TODO: Get proper profile FFU and get ChunkSizeInBytes + GPT GPT = new(GPTChunk); + FlashPart Part; + List FlashParts = new(); + + Partition NvBackupPartition = GPT.GetPartition("BACKUP_BS_NV"); + if (NvBackupPartition != null) + { + // This must be a left over of a half unlocked bootloader + Partition NvPartition = GPT.GetPartition("UEFI_BS_NV"); + NvBackupPartition.Name = "UEFI_BS_NV"; + NvBackupPartition.PartitionGuid = NvPartition.PartitionGuid; + NvBackupPartition.PartitionTypeGuid = NvPartition.PartitionTypeGuid; + GPT.Partitions.Remove(NvPartition); + + GPT.Rebuild(); + Part = new FlashPart + { + StartSector = 0, + Stream = new MemoryStream(GPTChunk) + }; + FlashParts.Add(Part); + } + + bool ClearFlashingStatus = true; + + // We should only clear NV if there was no backup NV to be restored and the current NV contains the SB unlock. + if ((NvBackupPartition == null) && !Info.UefiSecureBootEnabled) + { + // ClearNV + Part = new FlashPart(); + Partition Target = GPT.GetPartition("UEFI_BS_NV"); + Part.StartSector = (UInt32)Target.FirstSector; + Part.Stream = new MemoryStream(new byte[0x40000]); + FlashParts.Add(Part); + + ClearFlashingStatus = false; + } + + if (FlashParts.Count > 0) + { + ActivateSubContext(new BusyViewModel("Restoring bootloader...")); + WPinternalsStatus LastStatus = WPinternalsStatus.Undefined; + LumiaV2UnlockBootViewModel.LumiaV2CustomFlash(PhoneNotifier, FFUPath, false, false, FlashParts, true, ClearFlashingStatusAtEnd: ClearFlashingStatus, + SetWorkingStatus: (m, s, v, a, st) => + { + if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) + { + SetWorkingStatus(m, s, v, a, st); + } + else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) + { + SetWorkingStatus("Restoring bootloader...", null, null, Status: WPinternalsStatus.Flashing); + } + + LastStatus = st; + }, + UpdateWorkingStatus: (m, s, v, st) => + { + if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) + { + UpdateWorkingStatus(m, s, v, st); + } + else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) + { + SetWorkingStatus("Restoring bootloader...", null, null, Status: WPinternalsStatus.Flashing); + } + + LastStatus = st; + } + ).Wait(); + + if ((PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Flash)) + { + PhoneNotifier.WaitForArrival().Wait(); + } + + if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) + { + ((NokiaFlashModel)PhoneNotifier.CurrentModel).SwitchToFlashAppContext(); + } + } + } + + #endregion + + Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; + + ActivateSubContext(new BusyViewModel("Initializing flash...")); + + string ErrorSubMessage = null; + + try + { + FFU FFU = new(FFUPath); + BusyViewModel Busy = new("Flashing original FFU...", MaxProgressValue: FFU.TotalChunkCount, UIContext: UIContext); + ActivateSubContext(Busy); + byte Options = 0; + if (!Info.IsBootloaderSecure) + { + Options = (byte)((FlashOptions)Options | FlashOptions.SkipSignatureCheck); + } + + Phone.FlashFFU(FFU, Busy.ProgressUpdater, true, Options); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + if (Ex is WPinternalsException) + { + ErrorSubMessage = ((WPinternalsException)Ex).SubMessage; + } + + Result = false; + } + + if (!Result) + { + ExitFailure("Flash failed!", ErrorSubMessage); + return; + } + + ExitSuccess("Flash successful!", null); + }).Start(); + } + + // Called from an event-handler. So, "async void" is valid here. + internal async void FlashMMOS(string MMOSPath) + { + IsSwitchingInterface = true; // Prevents that a device is forced to Flash mode on this screen which is meant for flashing + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + FlashMMOSTask(MMOSPath); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal void FlashMMOSTask(string MMOSPath) + { + NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; + if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) + { + Phone.SwitchToFlashAppContext(); + } + + new Thread(() => + { + bool Result = true; + + ActivateSubContext(new BusyViewModel("Initializing flash...")); + + string ErrorSubMessage = null; + + try + { + FileInfo info = new(MMOSPath); + uint length = uint.Parse(info.Length.ToString()); + const int maximumbuffersize = 0x00240000; + uint totalcounts = (uint)Math.Truncate((decimal)length / maximumbuffersize); + BusyViewModel Busy = new("Flashing Test Mode package...", MaxProgressValue: totalcounts, UIContext: UIContext); + ActivateSubContext(Busy); + + Phone.FlashMMOS(MMOSPath, Busy.ProgressUpdater); + + ActivateSubContext(new BusyViewModel("And now booting phone to MMOS...", "If the phone stays on the lightning cog screen for a while, you may need to unplug and replug the phone to continue the boot process.")); + + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + if (Ex is WPinternalsException) + { + ErrorSubMessage = ((WPinternalsException)Ex).SubMessage; + } + + Result = false; + } + + if (!Result) + { + ExitFailure("Flash failed!", ErrorSubMessage); + return; + } + }).Start(); + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + + if (Args.NewInterface != PhoneInterfaces.Lumia_Label) + { + ExitFailure("Flash failed!", "Phone unexpectedly switched mode while booting MMOS image."); + return; + } + else + { + ExitSuccess("Flash successful!", null); + return; + } + } + + // Called from an event-handler. So, "async void" is valid here. + internal async void Exit() + { + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Normal, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + IsSwitchingInterface = false; + Callback(); + ActivateSubContext(null); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, () => + { + IsSwitchingInterface = false; + Callback(); + ActivateSubContext(null); + })); + } + } + + internal void ExitSuccess(string Message, string SubMessage) + { + MessageViewModel SuccessMessageViewModel = new(Message, () => + { + // No need to call Exit() to go to normal mode, because it already switches to normal mode automatically. + IsSwitchingInterface = false; // From here on a device will be forced to Flash mode again on this screen which is meant for flashing + Callback(); + ActivateSubContext(null); + }); + SuccessMessageViewModel.SubMessage = SubMessage; + ActivateSubContext(SuccessMessageViewModel); + } + + internal void ExitFailure(string Message, string SubMessage) + { + MessageViewModel ErrorMessageViewModel = new(Message, () => + { + IsSwitchingInterface = false; + Callback(); + ActivateSubContext(null); + }); + ErrorMessageViewModel.SubMessage = SubMessage; + ActivateSubContext(ErrorMessageViewModel); + } + } +} diff --git a/ViewModels/LumiaInfoViewModel.cs b/WPinternals/ViewModels/LumiaInfoViewModel.cs similarity index 97% rename from ViewModels/LumiaInfoViewModel.cs rename to WPinternals/ViewModels/LumiaInfoViewModel.cs index 179158e..b0db205 100644 --- a/ViewModels/LumiaInfoViewModel.cs +++ b/WPinternals/ViewModels/LumiaInfoViewModel.cs @@ -1,88 +1,88 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class LumiaInfoViewModel : ContextViewModel - { - internal PhoneInterfaces? CurrentInterface; - internal IDisposable CurrentModel; - internal PhoneNotifierViewModel PhoneNotifier; - private readonly Action ModeSwitchRequestCallback; - private readonly Action SwitchToGettingStarted; - - internal LumiaInfoViewModel(PhoneNotifierViewModel PhoneNotifier, Action ModeSwitchRequestCallback, Action SwitchToGettingStarted) - : base() - { - this.PhoneNotifier = PhoneNotifier; - this.ModeSwitchRequestCallback = ModeSwitchRequestCallback; - this.SwitchToGettingStarted = SwitchToGettingStarted; - - CurrentInterface = PhoneNotifier.CurrentInterface; - CurrentModel = PhoneNotifier.CurrentModel; - - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - PhoneNotifier.DeviceRemoved += DeviceRemoved; - } - - ~LumiaInfoViewModel() - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - PhoneNotifier.DeviceRemoved -= DeviceRemoved; - } - - private void DeviceRemoved() - { - CurrentInterface = null; - CurrentModel = null; - ActivateSubContext(null); - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - CurrentInterface = Args.NewInterface; - CurrentModel = Args.NewModel; - - // Determine SubcontextViewModel - switch (CurrentInterface) - { - case null: - case PhoneInterfaces.Lumia_Bootloader: - ActivateSubContext(null); - //ActivateSubContext(new NokiaBootloaderViewModel((NokiaFlashModel)CurrentModel, ModeSwitchRequestCallback, SwitchToGettingStarted)); - break; - case PhoneInterfaces.Lumia_Normal: - ActivateSubContext(new NokiaNormalViewModel((NokiaPhoneModel)CurrentModel, ModeSwitchRequestCallback)); - break; - case PhoneInterfaces.Lumia_Flash: - ActivateSubContext(new NokiaFlashViewModel((NokiaFlashModel)CurrentModel, ModeSwitchRequestCallback, SwitchToGettingStarted)); - break; - case PhoneInterfaces.Lumia_Label: - ActivateSubContext(new NokiaLabelViewModel((NokiaPhoneModel)CurrentModel, ModeSwitchRequestCallback)); - break; - case PhoneInterfaces.Lumia_MassStorage: - ActivateSubContext(new NokiaMassStorageViewModel((MassStorage)CurrentModel)); - break; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class LumiaInfoViewModel : ContextViewModel + { + internal PhoneInterfaces? CurrentInterface; + internal IDisposable CurrentModel; + internal PhoneNotifierViewModel PhoneNotifier; + private readonly Action ModeSwitchRequestCallback; + private readonly Action SwitchToGettingStarted; + + internal LumiaInfoViewModel(PhoneNotifierViewModel PhoneNotifier, Action ModeSwitchRequestCallback, Action SwitchToGettingStarted) + : base() + { + this.PhoneNotifier = PhoneNotifier; + this.ModeSwitchRequestCallback = ModeSwitchRequestCallback; + this.SwitchToGettingStarted = SwitchToGettingStarted; + + CurrentInterface = PhoneNotifier.CurrentInterface; + CurrentModel = PhoneNotifier.CurrentModel; + + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + PhoneNotifier.DeviceRemoved += DeviceRemoved; + } + + ~LumiaInfoViewModel() + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + PhoneNotifier.DeviceRemoved -= DeviceRemoved; + } + + private void DeviceRemoved() + { + CurrentInterface = null; + CurrentModel = null; + ActivateSubContext(null); + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + CurrentInterface = Args.NewInterface; + CurrentModel = Args.NewModel; + + // Determine SubcontextViewModel + switch (CurrentInterface) + { + case null: + case PhoneInterfaces.Lumia_Bootloader: + ActivateSubContext(null); + //ActivateSubContext(new NokiaBootloaderViewModel((NokiaFlashModel)CurrentModel, ModeSwitchRequestCallback, SwitchToGettingStarted)); + break; + case PhoneInterfaces.Lumia_Normal: + ActivateSubContext(new NokiaNormalViewModel((NokiaPhoneModel)CurrentModel, ModeSwitchRequestCallback)); + break; + case PhoneInterfaces.Lumia_Flash: + ActivateSubContext(new NokiaFlashViewModel((NokiaFlashModel)CurrentModel, ModeSwitchRequestCallback, SwitchToGettingStarted)); + break; + case PhoneInterfaces.Lumia_Label: + ActivateSubContext(new NokiaLabelViewModel((NokiaPhoneModel)CurrentModel, ModeSwitchRequestCallback)); + break; + case PhoneInterfaces.Lumia_MassStorage: + ActivateSubContext(new NokiaMassStorageViewModel((MassStorage)CurrentModel)); + break; + } + } + } +} diff --git a/ViewModels/LumiaModeViewModel.cs b/WPinternals/ViewModels/LumiaModeViewModel.cs similarity index 97% rename from ViewModels/LumiaModeViewModel.cs rename to WPinternals/ViewModels/LumiaModeViewModel.cs index c6fa9ad..fe4b035 100644 --- a/ViewModels/LumiaModeViewModel.cs +++ b/WPinternals/ViewModels/LumiaModeViewModel.cs @@ -1,132 +1,132 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class LumiaModeViewModel : ContextViewModel - { - internal PhoneInterfaces? CurrentInterface; - internal IDisposable CurrentModel; - internal PhoneNotifierViewModel PhoneNotifier; - private readonly Action Callback; - - internal LumiaModeViewModel(PhoneNotifierViewModel PhoneNotifier, Action Callback) - : base() - { - this.PhoneNotifier = PhoneNotifier; - this.Callback = Callback; - - CurrentInterface = PhoneNotifier.CurrentInterface; - CurrentModel = PhoneNotifier.CurrentModel; - - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - PhoneNotifier.DeviceRemoved += DeviceRemoved; - } - - ~LumiaModeViewModel() - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - PhoneNotifier.DeviceRemoved -= DeviceRemoved; - } - - private void DeviceRemoved() - { - CurrentInterface = null; - CurrentModel = null; - - if (!IsSwitchingInterface) - { - ActivateSubContext(null); - } - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - CurrentInterface = Args.NewInterface; - CurrentModel = Args.NewModel; - - if (!IsSwitchingInterface && IsActive) - { - Refresh(); - } - } - - internal override void EvaluateViewState() - { - if (!IsSwitchingInterface && IsActive) - { - Refresh(); - } - } - - private void Refresh() - { - // Determine SubcontextViewModel - switch (CurrentInterface) - { - case null: - ActivateSubContext(null); - break; - case PhoneInterfaces.Lumia_Bootloader: - ActivateSubContext(null); - //ActivateSubContext(new NokiaModeBootloaderViewModel((NokiaFlashModel)CurrentModel, OnModeSwitchRequested)); - break; - case PhoneInterfaces.Lumia_Normal: - ActivateSubContext(new NokiaModeNormalViewModel((NokiaPhoneModel)CurrentModel, OnModeSwitchRequested)); - break; - case PhoneInterfaces.Lumia_Flash: - ActivateSubContext(new NokiaModeFlashViewModel((NokiaFlashModel)CurrentModel, OnModeSwitchRequested)); - break; - case PhoneInterfaces.Lumia_Label: - ActivateSubContext(new NokiaModeLabelViewModel((NokiaPhoneModel)CurrentModel, OnModeSwitchRequested)); - break; - case PhoneInterfaces.Lumia_MassStorage: - ActivateSubContext(new NokiaModeMassStorageViewModel((MassStorage)CurrentModel, OnModeSwitchRequested)); - break; - } - } - - // Called from eventhandler, so "async void" is valid here. - internal async void OnModeSwitchRequested(PhoneInterfaces? TargetInterface) - { - IsSwitchingInterface = true; - - try - { - await SwitchModeViewModel.SwitchToWithStatus(PhoneNotifier, TargetInterface, SetWorkingStatus, UpdateWorkingStatus, null); // This is a manual switch. We don't care about which volume arrives. - - IsSwitchingInterface = false; - Callback(); - Refresh(); - } - catch (Exception Ex) - { - IsSwitchingInterface = false; - ActivateSubContext(new MessageViewModel(Ex.Message, () => - { - Callback(); - Refresh(); - })); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class LumiaModeViewModel : ContextViewModel + { + internal PhoneInterfaces? CurrentInterface; + internal IDisposable CurrentModel; + internal PhoneNotifierViewModel PhoneNotifier; + private readonly Action Callback; + + internal LumiaModeViewModel(PhoneNotifierViewModel PhoneNotifier, Action Callback) + : base() + { + this.PhoneNotifier = PhoneNotifier; + this.Callback = Callback; + + CurrentInterface = PhoneNotifier.CurrentInterface; + CurrentModel = PhoneNotifier.CurrentModel; + + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + PhoneNotifier.DeviceRemoved += DeviceRemoved; + } + + ~LumiaModeViewModel() + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + PhoneNotifier.DeviceRemoved -= DeviceRemoved; + } + + private void DeviceRemoved() + { + CurrentInterface = null; + CurrentModel = null; + + if (!IsSwitchingInterface) + { + ActivateSubContext(null); + } + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + CurrentInterface = Args.NewInterface; + CurrentModel = Args.NewModel; + + if (!IsSwitchingInterface && IsActive) + { + Refresh(); + } + } + + internal override void EvaluateViewState() + { + if (!IsSwitchingInterface && IsActive) + { + Refresh(); + } + } + + private void Refresh() + { + // Determine SubcontextViewModel + switch (CurrentInterface) + { + case null: + ActivateSubContext(null); + break; + case PhoneInterfaces.Lumia_Bootloader: + ActivateSubContext(null); + //ActivateSubContext(new NokiaModeBootloaderViewModel((NokiaFlashModel)CurrentModel, OnModeSwitchRequested)); + break; + case PhoneInterfaces.Lumia_Normal: + ActivateSubContext(new NokiaModeNormalViewModel((NokiaPhoneModel)CurrentModel, OnModeSwitchRequested)); + break; + case PhoneInterfaces.Lumia_Flash: + ActivateSubContext(new NokiaModeFlashViewModel((NokiaFlashModel)CurrentModel, OnModeSwitchRequested)); + break; + case PhoneInterfaces.Lumia_Label: + ActivateSubContext(new NokiaModeLabelViewModel((NokiaPhoneModel)CurrentModel, OnModeSwitchRequested)); + break; + case PhoneInterfaces.Lumia_MassStorage: + ActivateSubContext(new NokiaModeMassStorageViewModel((MassStorage)CurrentModel, OnModeSwitchRequested)); + break; + } + } + + // Called from eventhandler, so "async void" is valid here. + internal async void OnModeSwitchRequested(PhoneInterfaces? TargetInterface) + { + IsSwitchingInterface = true; + + try + { + await SwitchModeViewModel.SwitchToWithStatus(PhoneNotifier, TargetInterface, SetWorkingStatus, UpdateWorkingStatus, null); // This is a manual switch. We don't care about which volume arrives. + + IsSwitchingInterface = false; + Callback(); + Refresh(); + } + catch (Exception Ex) + { + IsSwitchingInterface = false; + ActivateSubContext(new MessageViewModel(Ex.Message, () => + { + Callback(); + Refresh(); + })); + } + } + } +} diff --git a/ViewModels/LumiaUnlockBootViewModel.cs b/WPinternals/ViewModels/LumiaUnlockBootViewModel.cs similarity index 97% rename from ViewModels/LumiaUnlockBootViewModel.cs rename to WPinternals/ViewModels/LumiaUnlockBootViewModel.cs index 1b1c709..4268a08 100644 --- a/ViewModels/LumiaUnlockBootViewModel.cs +++ b/WPinternals/ViewModels/LumiaUnlockBootViewModel.cs @@ -1,1105 +1,1105 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using Microsoft.Win32; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace WPinternals -{ - // Create this class on the UI thread, after the main-window of the application is initialized. - // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. - // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. - // Therefore the main window must be created before the ViewModel is created. - - internal enum MachineState - { - Default, - LumiaUnlockBoot - }; - - internal class LumiaUnlockBootViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - private readonly Action Callback; - private string FFUPath; - private string LoadersPath; - private string SBL3Path; - private string ProfileFFUPath; - private string EDEPath; - private string SupportedFFUPath; - private bool IsBootLoaderUnlocked; - private byte[] RootKeyHash = null; - private readonly Action SwitchToFlashRom; - private readonly Action SwitchToUndoRoot; - private readonly Action SwitchToDownload; - private readonly bool DoUnlock; - private MachineState State; - private readonly object EvaluateViewStateLockObject = new(); - - internal LumiaUnlockBootViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, bool DoUnlock, Action Callback) - : base() - { - IsSwitchingInterface = false; - IsFlashModeOperation = true; - - this.PhoneNotifier = PhoneNotifier; - this.SwitchToFlashRom = SwitchToFlashRom; - this.SwitchToUndoRoot = SwitchToUndoRoot; - this.SwitchToDownload = SwitchToDownload; - this.DoUnlock = DoUnlock; - this.Callback = Callback; - - State = MachineState.Default; - - this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - - // ViewState will be evaluated as soon as this object is set as DataContext - } - - ~LumiaUnlockBootViewModel() - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - } - - // Potentially blocking UI. Threadsafe. - internal override void EvaluateViewState() - { - if (!IsActive) - { - return; - } - - if (State == MachineState.LumiaUnlockBoot) - { - return; - } - - lock (EvaluateViewStateLockObject) - { - switch (PhoneNotifier.CurrentInterface) - { - case PhoneInterfaces.Lumia_Normal: - case PhoneInterfaces.Lumia_Label: - IsSwitchingInterface = false; - if (DoUnlock) - { - // Display View to switch to Flash mode - LogFile.Log("Start unlock. Phone needs to switch to Flash-mode"); - ActivateSubContext(new MessageViewModel("In order to start unlocking the bootloader, the phone needs to be switched to Flash-mode.", SwitchToFlashMode, Exit)); - } - else - { - // Display View to switch to Flash mode - LogFile.Log("Start boot restore. Phone needs to switch to Flash-mode"); - ActivateSubContext(new MessageViewModel("In order to start restoring the bootloader, the phone needs to be switched to Flash-mode.", SwitchToFlashMode, Exit)); - } - break; - case PhoneInterfaces.Lumia_Flash: - // Display View with device info and request for resources - // Click on "Continue" will start processing all resources - // Processing may fail with error message - // Or processing will succeed and user will again be asked to continue with Bricking-procedure (to switch to emergency mode) - - // This code is not always invoked by OnArrival event. - // So this is not always in a thread from the threadpool. - // So we need to avoid UI blocking code here. - - IsSwitchingInterface = false; - - int TestPos = 0; - - try // In case phone reboots during the time that status is being read - { - // Some phones, like Lumia 928 verizon, do not support the Terminal interface! - // To read the RootKeyHash we use ReadParam("RRKH"), instead of GetTerminalResponse().RootKeyHash. - RootKeyHash = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadParam("RRKH"); - - TestPos = 1; - - UefiSecurityStatusResponse SecurityStatus = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadSecurityStatus(); - if (SecurityStatus != null) - { - IsBootLoaderUnlocked = SecurityStatus.AuthenticationStatus || SecurityStatus.RdcStatus || !SecurityStatus.SecureFfuEfuseStatus; - } - - TestPos = 2; - - PhoneInfo Info = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(); - if (SecurityStatus == null) - { - IsBootLoaderUnlocked = !Info.IsBootloaderSecure; - } - - if (RootKeyHash == null) - { - RootKeyHash = Info.RKH ?? (new byte[32]); - } - - TestPos = 3; - - if (Info.FlashAppProtocolVersionMajor < 2) - { - // This action is executed after the resources are selected by the user. - void ReturnFunction(string FFUPath, string LoadersPath, string SBL3Path, string ProfileFFUPath, string EDEPath, string SupportedFFUPath, bool DoFixBoot) - { - // Stop responding to device arrival here, because all connections are handled by subfunctions, not here. - IsSwitchingInterface = true; - State = MachineState.LumiaUnlockBoot; - - // This is a callback on the UI thread - // Resources are confirmed by user - this.FFUPath = FFUPath; - this.LoadersPath = LoadersPath; - this.SBL3Path = SBL3Path; - this.SupportedFFUPath = SupportedFFUPath; - StorePaths(); - - LogFile.Log("Processing resources:"); - LogFile.Log("FFU: " + FFUPath); - LogFile.Log("Loaders: " + LoadersPath); - if (SBL3Path == null) - { - LogFile.Log("No SBL3 specified"); - } - else - { - LogFile.Log("SBL3: " + SBL3Path); - } - - ActivateSubContext(new BusyViewModel("Processing resources...")); - - if (DoUnlock) - { - Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1UnlockFirmware(PhoneNotifier, FFUPath, LoadersPath, SBL3Path, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); - } - else - { - Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1RelockFirmware(PhoneNotifier, FFUPath, LoadersPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); - } - } - - if (DoUnlock) - { - ActivateSubContext(new BootUnlockResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, false)); - } - else - { - ActivateSubContext(new BootRestoreResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, false)); - } - } - else - { - bool AlreadyUnlocked = false; - if (DoUnlock) - { - NokiaFlashModel FlashModel = (NokiaFlashModel)PhoneNotifier.CurrentModel; - GPT GPT = FlashModel.ReadGPT(); - if ((GPT.GetPartition("IS_UNLOCKED") != null) || (GPT.GetPartition("BACKUP_EFIESP") != null)) - { - //ExitMessage("Phone is already unlocked", null); - //return; - AlreadyUnlocked = true; - } - } - - TestPos = 4; - - // Stop responding to device arrival here, because all connections are handled by subfunctions, not here. - IsSwitchingInterface = true; - - // This action is executed after the resources are selected by the user. - void ReturnFunction(string FFUPath, string LoadersPath, string SBL3Path, string ProfileFFUPath, string EDEPath, string SupportedFFUPath, bool DoFixBoot) - { - IsSwitchingInterface = true; - State = MachineState.LumiaUnlockBoot; - if (DoUnlock) - { - // This is a callback on the UI thread - // Resources are confirmed by user - this.ProfileFFUPath = ProfileFFUPath; - this.EDEPath = EDEPath; - this.SupportedFFUPath = SupportedFFUPath; - StorePaths(); - - if (DoFixBoot) - { - LogFile.Log("Fix Boot"); - } - else - { - LogFile.Log("Unlock Bootloader"); - } - - LogFile.Log("Processing resources:"); - LogFile.Log("Profile FFU: " + ProfileFFUPath); - LogFile.Log("EDE file: " + EDEPath); - if (SupportedFFUPath != null) - { - LogFile.Log("Donor-FFU with supported OS version: " + SupportedFFUPath); - } - - Task.Run(async () => - { - if (DoFixBoot) - { - await LumiaV2UnlockBootViewModel.LumiaV2FixBoot(PhoneNotifier, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage); - } - else if (!AlreadyUnlocked) - { - await LumiaUnlockBootloaderViewModel.LumiaV2UnlockUEFI(PhoneNotifier, ProfileFFUPath, EDEPath, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage); - } - else - { - await LumiaUnlockBootloaderViewModel.LumiaV2UnlockUEFI(PhoneNotifier, ProfileFFUPath, EDEPath, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage, true); - } - }); - } - else - { - Task.Run(async () => - { - FFU ProfileFFU = null; - - List FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); - ProfileFFU = FFUs.Count > 0 - ? new FFU(FFUs[0].Path) - : throw new WPinternalsException("Profile FFU missing", "No profile FFU has been found in the repository for your device. You can add a profile FFU within the download section of the tool or by using the command line."); - - LogFile.Log("Profile FFU: " + ProfileFFU.Path); - - await LumiaUnlockBootloaderViewModel.LumiaV2RelockUEFI(PhoneNotifier, ProfileFFU.Path, true, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage); - }); - } - } - - TestPos = 5; - - if (DoUnlock) - { - ActivateSubContext(new BootUnlockResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, true, Info.PlatformID, Info.Type)); - } - else - { - ActivateSubContext(new BootRestoreResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, true, Info.PlatformID, Info.Type)); - } - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex, LogType.FileAndConsole, TestPos.ToString()); - } - break; - case PhoneInterfaces.Qualcomm_Download: - IsSwitchingInterface = false; - - // If resources are not confirmed yet, then display view with device info and request for resources. - QualcommDownload Download = new((QualcommSerial)PhoneNotifier.CurrentModel); - byte[] QualcommRootKeyHash; - - try - { - QualcommRootKeyHash = Download.GetRKH(); - } - catch (BadConnectionException) - { - // This is a Spec B device - break; - } - - if (RootKeyHash == null) - { - RootKeyHash = QualcommRootKeyHash; - } - else if (!StructuralComparisons.StructuralEqualityComparer.Equals(RootKeyHash, QualcommRootKeyHash)) - { - LogFile.Log("Error: Root Key Hash in Qualcomm Emergency mode does not match!"); - ActivateSubContext(new MessageViewModel("Error: Root Key Hash in Qualcomm Emergency mode does not match!", Callback)); - return; - } - - // This action is executed after the user selected the resources. - Action ReturnFunctionD = (FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, SupportedFFUPath, DoFixBoot) => - { - IsSwitchingInterface = true; - State = MachineState.LumiaUnlockBoot; - // This is a callback on the UI thread - // Resources are confirmed by user - this.FFUPath = FFUPath; - this.LoadersPath = LoadersPath; - this.SBL3Path = SBL3Path; - StorePaths(); - - LogFile.Log("Processing resources:"); - LogFile.Log("FFU: " + FFUPath); - LogFile.Log("Loaders: " + LoadersPath); - if (SBL3Path == null) - { - LogFile.Log("No SBL3 specified"); - } - else - { - LogFile.Log("SBL3: " + SBL3Path); - } - - ActivateSubContext(new BusyViewModel("Processing resources...")); - - if (DoUnlock) - { - Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1UnlockFirmware(PhoneNotifier, FFUPath, LoadersPath, SBL3Path, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); - } - else - { - Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1RelockFirmware(PhoneNotifier, FFUPath, LoadersPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); - } - }; - - if (DoUnlock) - { - ActivateSubContext(new BootUnlockResourcesViewModel("Qualcomm Emergency Download mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunctionD, Abort, IsBootLoaderUnlocked, false)); - } - else - { - ActivateSubContext(new BootRestoreResourcesViewModel("Qualcomm Emergency Download mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunctionD, Abort, IsBootLoaderUnlocked, false)); - } - - break; - case PhoneInterfaces.Qualcomm_Flash: - { - IsSwitchingInterface = true; - State = MachineState.LumiaUnlockBoot; - ActivateSubContext(new BusyViewModel("Recovering resources...")); - - LogFile.Log("Phone was unexpectedly detected in this mode while resources were not loaded yet."); - LogFile.Log("WPInternals tool probably crashed in previous session."); - LogFile.Log("Trying to recover resources from the registry."); - - FFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "FFUPath", null); - SupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); - LoadersPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "LoadersPath", null); - SBL3Path = DoUnlock ? (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SBL3Path", null) : null; - - if (DoUnlock) - { - Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1UnlockFirmware(PhoneNotifier, FFUPath, LoadersPath, SBL3Path, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); - } - else - { - Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1RelockFirmware(PhoneNotifier, FFUPath, LoadersPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); - } - break; - } - default: - // Show View "Waiting for connection" - IsSwitchingInterface = false; - ActivateSubContext(null); - break; - } - } - } - - private void SwitchToFlashMode() - { - // SwitchModeViewModel must be created on the UI thread - IsSwitchingInterface = true; - UIContext.Post(async (t) => - { - LogFile.Log("Switching to Flash-mode"); - - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - }, null); - } - - private void Abort() - { - // SwitchModeViewModel must be created on the UI thread - IsSwitchingInterface = false; - UIContext.Post((t) => - { - StorePaths(); - LogFile.Log("Aborting."); - Exit(); - }, null); - } - - private void StorePaths() - { - RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\WPInternals", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\WPInternals"); - - if (FFUPath == null) - { - if (Key.GetValue("FFUPath") != null) - { - Key.DeleteValue("FFUPath"); - } - } - else - { - Key.SetValue("FFUPath", FFUPath); - } - - if (LoadersPath == null) - { - if (Key.GetValue("LoadersPath") != null) - { - Key.DeleteValue("LoadersPath"); - } - } - else - { - Key.SetValue("LoadersPath", LoadersPath); - } - - if (DoUnlock) - { - if (SBL3Path == null) - { - if (Key.GetValue("SBL3Path") != null) - { - Key.DeleteValue("SBL3Path"); - } - } - else - { - Key.SetValue("SBL3Path", SBL3Path); - } - } - - if (ProfileFFUPath == null) - { - if (Key.GetValue("ProfileFFUPath") != null) - { - Key.DeleteValue("ProfileFFUPath"); - } - } - else - { - Key.SetValue("ProfileFFUPath", ProfileFFUPath); - - App.Config.AddFfuToRepository(ProfileFFUPath); - } - - if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Download && PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Flash) - { - NokiaFlashModel Model = (NokiaFlashModel)PhoneNotifier.CurrentModel; - PhoneInfo Info = Model.ReadPhoneInfo(); - - if (EDEPath == null) - { - if (Key.GetValue("EDEPath") != null) - { - Key.DeleteValue("EDEPath"); - } - } - else - { - Key.SetValue("EDEPath", EDEPath); - - App.Config.AddEmergencyToRepository(Info.Type, EDEPath, null); - } - } - - if (SupportedFFUPath != null) - { - Key.SetValue("SupportedFFUPath", SupportedFFUPath); - - App.Config.AddFfuToRepository(SupportedFFUPath); - } - - Key.Close(); - } - - private void Exit() - { - IsSwitchingInterface = false; // From here on a device will be forced to Flash mode again on this screen which is meant for flashing - - FFUPath = null; - LoadersPath = null; - SBL3Path = null; - - Callback(); - ActivateSubContext(null); - } - - internal void ExitMessage(string Message, string SubMessage) - { - // SecureBoot Unlock v2 is done. Reactivate phone arrival events. - MessageViewModel SuccessMessageViewModel = new(Message, () => - { - State = MachineState.Default; - Exit(); - }); - SuccessMessageViewModel.SubMessage = SubMessage; - ActivateSubContext(SuccessMessageViewModel); - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - // Do not start on a new thread, because EvaluateViewState will also create new ViewModels and those should be created on the UI thread. - EvaluateViewState(); - } - } - - internal class BootUnlockResourcesViewModel : FlashResourcesViewModel - { - internal BootUnlockResourcesViewModel(string CurrentMode, byte[] RootKeyHash, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, Action Result, Action Abort, bool IsBootLoaderUnlocked, bool TargetHasNewFlashProtocol, string PlatformID = null, string ProductType = null) - : base(CurrentMode, RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, Result, Abort, IsBootLoaderUnlocked, TargetHasNewFlashProtocol, PlatformID, ProductType) - { - SBL3Path = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SBL3Path", null); - } - } - - internal class BootRestoreResourcesViewModel : FlashResourcesViewModel - { - internal BootRestoreResourcesViewModel(string CurrentMode, byte[] RootKeyHash, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, Action Result, Action Abort, bool IsBootLoaderUnlocked, bool TargetHasNewFlashProtocol, string PlatformID = null, string ProductType = null) - : base(CurrentMode, RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, Result, Abort, IsBootLoaderUnlocked, TargetHasNewFlashProtocol, PlatformID, ProductType) - { - SBL3Path = null; - } - } - - internal class FlashResourcesViewModel : ContextViewModel - { - internal Action SwitchToFlashRom; - internal Action SwitchToUndoRoot; - internal Action SwitchToDownload; - private readonly string PlatformID; - private readonly string ProductType; - private string ValidatedSupportedFfuPath = null; - - internal FlashResourcesViewModel(string CurrentMode, byte[] RootKeyHash, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, Action Result, Action Abort, bool IsBootLoaderUnlocked, bool TargetHasNewFlashProtocol, string PlatformID = null, string ProductType = null) : base() - { - IsSwitchingInterface = true; - this.CurrentMode = CurrentMode; - this.RootKeyHash = RootKeyHash; - this.PlatformID = PlatformID; - this.ProductType = ProductType; - this.SwitchToFlashRom = SwitchToFlashRom; - this.SwitchToUndoRoot = SwitchToUndoRoot; - this.SwitchToDownload = SwitchToDownload; - this.IsBootLoaderUnlocked = IsBootLoaderUnlocked; - OkCommand = new DelegateCommand(() => Result(FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, IsSupportedFfuNeeded ? SupportedFFUPath : null, false), - () => (!TargetHasNewFlashProtocol && (!IsSupportedFfuNeeded || (IsSupportedFfuValid && (SupportedFFUPath != null)))) || ((ProfileFFUPath != null) && (!IsSupportedFfuNeeded || (IsSupportedFfuValid && (SupportedFFUPath != null))))); - FixCommand = new DelegateCommand(() => Result(null, null, null, null, null, null, true)); - CancelCommand = new DelegateCommand(Abort); - this.TargetHasNewFlashProtocol = TargetHasNewFlashProtocol; - - if (TargetHasNewFlashProtocol) - { - SetProfileFFUPath(); - SetEDEPath(); - } - else - { - FFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "FFUPath", null); - SupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); - LoadersPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "LoadersPath", null); - } - } - - private void SetProfileFFUPath() - { - FFU ProfileFFU; - - try - { - if (_ProfileFFUPath != null) - { - ProfileFFU = new FFU(_ProfileFFUPath); - if (PlatformID.StartsWith(ProfileFFU.PlatformID, StringComparison.OrdinalIgnoreCase)) - { - IsProfileFfuValid = true; - return; - } - } - } - catch { } - - try - { - string TempProfileFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "ProfileFFUPath", null); - if (TempProfileFFUPath != null) - { - ProfileFFU = new FFU(TempProfileFFUPath); - if (PlatformID.StartsWith(ProfileFFU.PlatformID, StringComparison.OrdinalIgnoreCase)) - { - ProfileFFUPath = TempProfileFFUPath; - IsProfileFfuValid = true; - return; - } - } - } - catch { } - - List FFUs = App.Config.FFURepository.Where(e => PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); - if (FFUs.Count > 0) - { - IsProfileFfuValid = true; - ProfileFFUPath = FFUs[0].Path; - } - else - { - IsProfileFfuValid = false; - } - } - - private void SetEDEPath() - { - QualcommPartition Programmer; - string TempEDEPath; - - try - { - if (_EDEPath != null) - { - Programmer = new QualcommPartition(_EDEPath); - if (ByteOperations.Compare(Programmer.RootKeyHash, RootKeyHash)) - { - return; - } - } - } - catch { } - - try - { - TempEDEPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "EDEPath", null); - if (TempEDEPath != null) - { - Programmer = new QualcommPartition(TempEDEPath); - if (ByteOperations.Compare(Programmer.RootKeyHash, RootKeyHash)) - { - EDEPath = TempEDEPath; - return; - } - } - } - catch { } - - TempEDEPath = LumiaV2UnlockBootViewModel.GetProgrammerPath(RootKeyHash, ProductType); - if (TempEDEPath != null) - { - Programmer = new QualcommPartition(TempEDEPath); - if (ByteOperations.Compare(Programmer.RootKeyHash, RootKeyHash)) - { - EDEPath = TempEDEPath; - return; - } - } - } - - private void SetSupportedFFUPath() - { - if (!TargetHasNewFlashProtocol) - { - if (this is BootRestoreResourcesViewModel) - { - IsSupportedFfuNeeded = false; - return; - } - - try - { - FFU FFU = new(_FFUPath); - IsSupportedFfuNeeded = !App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == FFU.GetOSVersion()); - } - catch - { - IsSupportedFfuNeeded = false; - return; - } - - if (IsSupportedFfuNeeded) - { - FFU SupportedFFU; - - try - { - if (_SupportedFFUPath != null) - { - SupportedFFU = new FFU(_SupportedFFUPath); - if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) - { - return; - } - } - } - catch { } - - try - { - string TempSupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); - if (TempSupportedFFUPath != null) - { - SupportedFFU = new FFU(TempSupportedFFUPath); - if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) - { - ValidatedSupportedFfuPath = TempSupportedFFUPath; - SupportedFFUPath = TempSupportedFFUPath; - IsSupportedFfuValid = true; - return; - } - } - } - catch { } - - List FFUs = App.Config.FFURepository.Where(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion)).ToList(); - if (FFUs.Count > 0) - { - ValidatedSupportedFfuPath = FFUs[0].Path; - SupportedFFUPath = FFUs[0].Path; - IsSupportedFfuValid = true; - } - } - } - else - { - if ((this is BootRestoreResourcesViewModel) || (_ProfileFFUPath == null)) - { - IsSupportedFfuNeeded = false; - return; - } - - try - { - FFU ProfileFFU = new(_ProfileFFUPath); - IsSupportedFfuNeeded = !App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == ProfileFFU.GetOSVersion()); - } - catch - { - IsSupportedFfuNeeded = false; - return; - } - - if (IsSupportedFfuNeeded) - { - FFU SupportedFFU; - - try - { - if (_SupportedFFUPath != null) - { - SupportedFFU = new FFU(_SupportedFFUPath); - if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) - { - return; - } - } - } - catch { } - - try - { - string TempSupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); - if (TempSupportedFFUPath != null) - { - SupportedFFU = new FFU(TempSupportedFFUPath); - if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) - { - ValidatedSupportedFfuPath = TempSupportedFFUPath; - SupportedFFUPath = TempSupportedFFUPath; - IsSupportedFfuValid = true; - return; - } - } - } - catch { } - - List FFUs = App.Config.FFURepository.Where(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion)).ToList(); - if (FFUs.Count > 0) - { - ValidatedSupportedFfuPath = FFUs[0].Path; - SupportedFFUPath = FFUs[0].Path; - IsSupportedFfuValid = true; - } - } - } - } - - private void ValidateSupportedFfuPath() - { - try - { - if (IsSupportedFfuNeeded) - { - if (SupportedFFUPath == null) - { - IsSupportedFfuValid = true; // No visible warning when there is no SupportedFFU selected yet. - } - else - { - if (!TargetHasNewFlashProtocol) - { - if (App.Config.FFURepository.Any(e => (e.Path == SupportedFFUPath) && App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - IsSupportedFfuValid = true; - } - else - { - FFU SupportedFFU = new(SupportedFFUPath); - IsSupportedFfuValid = App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion()); - } - } - else - { - if (App.Config.FFURepository.Any(e => (e.Path == SupportedFFUPath) && App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) - { - IsSupportedFfuValid = true; - } - else - { - FFU SupportedFFU = new(SupportedFFUPath); - IsSupportedFfuValid = App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion()); - } - } - } - } - else - { - IsSupportedFfuValid = true; - } - - if (IsSupportedFfuValid && (SupportedFFUPath != null)) - { - ValidatedSupportedFfuPath = SupportedFFUPath; - } - } - catch - { - IsSupportedFfuValid = false; - } - } - - private bool _IsSupportedFfuNeeded = false; - public bool IsSupportedFfuNeeded - { - get - { - return _IsSupportedFfuNeeded; - } - set - { - _IsSupportedFfuNeeded = value; - OnPropertyChanged(nameof(IsSupportedFfuNeeded)); - OkCommand.RaiseCanExecuteChanged(); - } - } - - private bool _IsSupportedFfuValid = true; - public bool IsSupportedFfuValid - { - get - { - return _IsSupportedFfuValid; - } - set - { - _IsSupportedFfuValid = value; - OnPropertyChanged(nameof(IsSupportedFfuValid)); - OkCommand.RaiseCanExecuteChanged(); - } - } - - private bool _IsProfileFfuValid = true; - public bool IsProfileFfuValid - { - get - { - return _IsProfileFfuValid; - } - set - { - _IsProfileFfuValid = value; - OnPropertyChanged(nameof(IsProfileFfuValid)); - } - } - - private bool _TargetHasNewFlashProtocol = false; - public bool TargetHasNewFlashProtocol - { - get - { - return _TargetHasNewFlashProtocol; - } - set - { - _TargetHasNewFlashProtocol = value; - OnPropertyChanged(nameof(TargetHasNewFlashProtocol)); - OkCommand.RaiseCanExecuteChanged(); - } - } - - private bool _IsBootLoaderUnlocked = false; - public bool IsBootLoaderUnlocked - { - get - { - return _IsBootLoaderUnlocked; - } - set - { - _IsBootLoaderUnlocked = value; - OnPropertyChanged(nameof(IsBootLoaderUnlocked)); - } - } - - private string _FFUPath = null; - public string FFUPath - { - get - { - return _FFUPath; - } - set - { - _FFUPath = value; - OnPropertyChanged(nameof(FFUPath)); - SetSupportedFFUPath(); - OkCommand.RaiseCanExecuteChanged(); - } - } - - private string _ProfileFFUPath = null; - public string ProfileFFUPath - { - get - { - return _ProfileFFUPath; - } - set - { - if (_ProfileFFUPath != value) - { - _ProfileFFUPath = value; - OnPropertyChanged(nameof(ProfileFFUPath)); - SetSupportedFFUPath(); - OkCommand.RaiseCanExecuteChanged(); - } - } - } - - private string _SupportedFFUPath = null; - public string SupportedFFUPath - { - get - { - return _SupportedFFUPath; - } - set - { - if (_SupportedFFUPath != value) - { - _SupportedFFUPath = value; - OnPropertyChanged(nameof(SupportedFFUPath)); - - if (value != ValidatedSupportedFfuPath) - { - ValidateSupportedFfuPath(); - } - } - } - } - - private string _LoadersPath = null; - public string LoadersPath - { - get - { - return _LoadersPath; - } - set - { - _LoadersPath = value; - OnPropertyChanged(nameof(LoadersPath)); - } - } - - private string _EDEPath = null; - public string EDEPath - { - get - { - return _EDEPath; - } - set - { - _EDEPath = value; - OnPropertyChanged(nameof(EDEPath)); - } - } - - private string _SBL3Path = null; - public string SBL3Path - { - get - { - return _SBL3Path; - } - set - { - _SBL3Path = value; - OnPropertyChanged(nameof(SBL3Path)); - } - } - - private string _CurrentMode = null; - public string CurrentMode - { - get - { - return _CurrentMode; - } - set - { - _CurrentMode = value; - OnPropertyChanged(nameof(CurrentMode)); - } - } - - private byte[] _RootKeyHash = null; - public byte[] RootKeyHash - { - get - { - return _RootKeyHash; - } - set - { - _RootKeyHash = value; - OnPropertyChanged(nameof(RootKeyHash)); - } - } - public DelegateCommand OkCommand { get; } = null; - public DelegateCommand CancelCommand { get; } = null; - public DelegateCommand FixCommand { get; } = null; - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WPinternals +{ + // Create this class on the UI thread, after the main-window of the application is initialized. + // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. + // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. + // Therefore the main window must be created before the ViewModel is created. + + internal enum MachineState + { + Default, + LumiaUnlockBoot + }; + + internal class LumiaUnlockBootViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + private readonly Action Callback; + private string FFUPath; + private string LoadersPath; + private string SBL3Path; + private string ProfileFFUPath; + private string EDEPath; + private string SupportedFFUPath; + private bool IsBootLoaderUnlocked; + private byte[] RootKeyHash = null; + private readonly Action SwitchToFlashRom; + private readonly Action SwitchToUndoRoot; + private readonly Action SwitchToDownload; + private readonly bool DoUnlock; + private MachineState State; + private readonly object EvaluateViewStateLockObject = new(); + + internal LumiaUnlockBootViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, bool DoUnlock, Action Callback) + : base() + { + IsSwitchingInterface = false; + IsFlashModeOperation = true; + + this.PhoneNotifier = PhoneNotifier; + this.SwitchToFlashRom = SwitchToFlashRom; + this.SwitchToUndoRoot = SwitchToUndoRoot; + this.SwitchToDownload = SwitchToDownload; + this.DoUnlock = DoUnlock; + this.Callback = Callback; + + State = MachineState.Default; + + this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + + // ViewState will be evaluated as soon as this object is set as DataContext + } + + ~LumiaUnlockBootViewModel() + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + } + + // Potentially blocking UI. Threadsafe. + internal override void EvaluateViewState() + { + if (!IsActive) + { + return; + } + + if (State == MachineState.LumiaUnlockBoot) + { + return; + } + + lock (EvaluateViewStateLockObject) + { + switch (PhoneNotifier.CurrentInterface) + { + case PhoneInterfaces.Lumia_Normal: + case PhoneInterfaces.Lumia_Label: + IsSwitchingInterface = false; + if (DoUnlock) + { + // Display View to switch to Flash mode + LogFile.Log("Start unlock. Phone needs to switch to Flash-mode"); + ActivateSubContext(new MessageViewModel("In order to start unlocking the bootloader, the phone needs to be switched to Flash-mode.", SwitchToFlashMode, Exit)); + } + else + { + // Display View to switch to Flash mode + LogFile.Log("Start boot restore. Phone needs to switch to Flash-mode"); + ActivateSubContext(new MessageViewModel("In order to start restoring the bootloader, the phone needs to be switched to Flash-mode.", SwitchToFlashMode, Exit)); + } + break; + case PhoneInterfaces.Lumia_Flash: + // Display View with device info and request for resources + // Click on "Continue" will start processing all resources + // Processing may fail with error message + // Or processing will succeed and user will again be asked to continue with Bricking-procedure (to switch to emergency mode) + + // This code is not always invoked by OnArrival event. + // So this is not always in a thread from the threadpool. + // So we need to avoid UI blocking code here. + + IsSwitchingInterface = false; + + int TestPos = 0; + + try // In case phone reboots during the time that status is being read + { + // Some phones, like Lumia 928 verizon, do not support the Terminal interface! + // To read the RootKeyHash we use ReadParam("RRKH"), instead of GetTerminalResponse().RootKeyHash. + RootKeyHash = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadParam("RRKH"); + + TestPos = 1; + + UefiSecurityStatusResponse SecurityStatus = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadSecurityStatus(); + if (SecurityStatus != null) + { + IsBootLoaderUnlocked = SecurityStatus.AuthenticationStatus || SecurityStatus.RdcStatus || !SecurityStatus.SecureFfuEfuseStatus; + } + + TestPos = 2; + + PhoneInfo Info = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(); + if (SecurityStatus == null) + { + IsBootLoaderUnlocked = !Info.IsBootloaderSecure; + } + + if (RootKeyHash == null) + { + RootKeyHash = Info.RKH ?? (new byte[32]); + } + + TestPos = 3; + + if (Info.FlashAppProtocolVersionMajor < 2) + { + // This action is executed after the resources are selected by the user. + void ReturnFunction(string FFUPath, string LoadersPath, string SBL3Path, string ProfileFFUPath, string EDEPath, string SupportedFFUPath, bool DoFixBoot) + { + // Stop responding to device arrival here, because all connections are handled by subfunctions, not here. + IsSwitchingInterface = true; + State = MachineState.LumiaUnlockBoot; + + // This is a callback on the UI thread + // Resources are confirmed by user + this.FFUPath = FFUPath; + this.LoadersPath = LoadersPath; + this.SBL3Path = SBL3Path; + this.SupportedFFUPath = SupportedFFUPath; + StorePaths(); + + LogFile.Log("Processing resources:"); + LogFile.Log("FFU: " + FFUPath); + LogFile.Log("Loaders: " + LoadersPath); + if (SBL3Path == null) + { + LogFile.Log("No SBL3 specified"); + } + else + { + LogFile.Log("SBL3: " + SBL3Path); + } + + ActivateSubContext(new BusyViewModel("Processing resources...")); + + if (DoUnlock) + { + Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1UnlockFirmware(PhoneNotifier, FFUPath, LoadersPath, SBL3Path, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); + } + else + { + Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1RelockFirmware(PhoneNotifier, FFUPath, LoadersPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); + } + } + + if (DoUnlock) + { + ActivateSubContext(new BootUnlockResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, false)); + } + else + { + ActivateSubContext(new BootRestoreResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, false)); + } + } + else + { + bool AlreadyUnlocked = false; + if (DoUnlock) + { + NokiaFlashModel FlashModel = (NokiaFlashModel)PhoneNotifier.CurrentModel; + GPT GPT = FlashModel.ReadGPT(); + if ((GPT.GetPartition("IS_UNLOCKED") != null) || (GPT.GetPartition("BACKUP_EFIESP") != null)) + { + //ExitMessage("Phone is already unlocked", null); + //return; + AlreadyUnlocked = true; + } + } + + TestPos = 4; + + // Stop responding to device arrival here, because all connections are handled by subfunctions, not here. + IsSwitchingInterface = true; + + // This action is executed after the resources are selected by the user. + void ReturnFunction(string FFUPath, string LoadersPath, string SBL3Path, string ProfileFFUPath, string EDEPath, string SupportedFFUPath, bool DoFixBoot) + { + IsSwitchingInterface = true; + State = MachineState.LumiaUnlockBoot; + if (DoUnlock) + { + // This is a callback on the UI thread + // Resources are confirmed by user + this.ProfileFFUPath = ProfileFFUPath; + this.EDEPath = EDEPath; + this.SupportedFFUPath = SupportedFFUPath; + StorePaths(); + + if (DoFixBoot) + { + LogFile.Log("Fix Boot"); + } + else + { + LogFile.Log("Unlock Bootloader"); + } + + LogFile.Log("Processing resources:"); + LogFile.Log("Profile FFU: " + ProfileFFUPath); + LogFile.Log("EDE file: " + EDEPath); + if (SupportedFFUPath != null) + { + LogFile.Log("Donor-FFU with supported OS version: " + SupportedFFUPath); + } + + Task.Run(async () => + { + if (DoFixBoot) + { + await LumiaV2UnlockBootViewModel.LumiaV2FixBoot(PhoneNotifier, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage); + } + else if (!AlreadyUnlocked) + { + await LumiaUnlockBootloaderViewModel.LumiaV2UnlockUEFI(PhoneNotifier, ProfileFFUPath, EDEPath, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage); + } + else + { + await LumiaUnlockBootloaderViewModel.LumiaV2UnlockUEFI(PhoneNotifier, ProfileFFUPath, EDEPath, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage, true); + } + }); + } + else + { + Task.Run(async () => + { + FFU ProfileFFU = null; + + List FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); + ProfileFFU = FFUs.Count > 0 + ? new FFU(FFUs[0].Path) + : throw new WPinternalsException("Profile FFU missing", "No profile FFU has been found in the repository for your device. You can add a profile FFU within the download section of the tool or by using the command line."); + + LogFile.Log("Profile FFU: " + ProfileFFU.Path); + + await LumiaUnlockBootloaderViewModel.LumiaV2RelockUEFI(PhoneNotifier, ProfileFFU.Path, true, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage); + }); + } + } + + TestPos = 5; + + if (DoUnlock) + { + ActivateSubContext(new BootUnlockResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, true, Info.PlatformID, Info.Type)); + } + else + { + ActivateSubContext(new BootRestoreResourcesViewModel("Lumia Flash mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, true, Info.PlatformID, Info.Type)); + } + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex, LogType.FileAndConsole, TestPos.ToString()); + } + break; + case PhoneInterfaces.Qualcomm_Download: + IsSwitchingInterface = false; + + // If resources are not confirmed yet, then display view with device info and request for resources. + QualcommDownload Download = new((QualcommSerial)PhoneNotifier.CurrentModel); + byte[] QualcommRootKeyHash; + + try + { + QualcommRootKeyHash = Download.GetRKH(); + } + catch (BadConnectionException) + { + // This is a Spec B device + break; + } + + if (RootKeyHash == null) + { + RootKeyHash = QualcommRootKeyHash; + } + else if (!StructuralComparisons.StructuralEqualityComparer.Equals(RootKeyHash, QualcommRootKeyHash)) + { + LogFile.Log("Error: Root Key Hash in Qualcomm Emergency mode does not match!"); + ActivateSubContext(new MessageViewModel("Error: Root Key Hash in Qualcomm Emergency mode does not match!", Callback)); + return; + } + + // This action is executed after the user selected the resources. + Action ReturnFunctionD = (FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, SupportedFFUPath, DoFixBoot) => + { + IsSwitchingInterface = true; + State = MachineState.LumiaUnlockBoot; + // This is a callback on the UI thread + // Resources are confirmed by user + this.FFUPath = FFUPath; + this.LoadersPath = LoadersPath; + this.SBL3Path = SBL3Path; + StorePaths(); + + LogFile.Log("Processing resources:"); + LogFile.Log("FFU: " + FFUPath); + LogFile.Log("Loaders: " + LoadersPath); + if (SBL3Path == null) + { + LogFile.Log("No SBL3 specified"); + } + else + { + LogFile.Log("SBL3: " + SBL3Path); + } + + ActivateSubContext(new BusyViewModel("Processing resources...")); + + if (DoUnlock) + { + Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1UnlockFirmware(PhoneNotifier, FFUPath, LoadersPath, SBL3Path, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); + } + else + { + Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1RelockFirmware(PhoneNotifier, FFUPath, LoadersPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); + } + }; + + if (DoUnlock) + { + ActivateSubContext(new BootUnlockResourcesViewModel("Qualcomm Emergency Download mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunctionD, Abort, IsBootLoaderUnlocked, false)); + } + else + { + ActivateSubContext(new BootRestoreResourcesViewModel("Qualcomm Emergency Download mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunctionD, Abort, IsBootLoaderUnlocked, false)); + } + + break; + case PhoneInterfaces.Qualcomm_Flash: + { + IsSwitchingInterface = true; + State = MachineState.LumiaUnlockBoot; + ActivateSubContext(new BusyViewModel("Recovering resources...")); + + LogFile.Log("Phone was unexpectedly detected in this mode while resources were not loaded yet."); + LogFile.Log("WPInternals tool probably crashed in previous session."); + LogFile.Log("Trying to recover resources from the registry."); + + FFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "FFUPath", null); + SupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); + LoadersPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "LoadersPath", null); + SBL3Path = DoUnlock ? (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SBL3Path", null) : null; + + if (DoUnlock) + { + Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1UnlockFirmware(PhoneNotifier, FFUPath, LoadersPath, SBL3Path, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); + } + else + { + Task.Run(async () => await LumiaUnlockBootloaderViewModel.LumiaV1RelockFirmware(PhoneNotifier, FFUPath, LoadersPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage)); + } + break; + } + default: + // Show View "Waiting for connection" + IsSwitchingInterface = false; + ActivateSubContext(null); + break; + } + } + } + + private void SwitchToFlashMode() + { + // SwitchModeViewModel must be created on the UI thread + IsSwitchingInterface = true; + UIContext.Post(async (t) => + { + LogFile.Log("Switching to Flash-mode"); + + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + }, null); + } + + private void Abort() + { + // SwitchModeViewModel must be created on the UI thread + IsSwitchingInterface = false; + UIContext.Post((t) => + { + StorePaths(); + LogFile.Log("Aborting."); + Exit(); + }, null); + } + + private void StorePaths() + { + RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\WPInternals", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\WPInternals"); + + if (FFUPath == null) + { + if (Key.GetValue("FFUPath") != null) + { + Key.DeleteValue("FFUPath"); + } + } + else + { + Key.SetValue("FFUPath", FFUPath); + } + + if (LoadersPath == null) + { + if (Key.GetValue("LoadersPath") != null) + { + Key.DeleteValue("LoadersPath"); + } + } + else + { + Key.SetValue("LoadersPath", LoadersPath); + } + + if (DoUnlock) + { + if (SBL3Path == null) + { + if (Key.GetValue("SBL3Path") != null) + { + Key.DeleteValue("SBL3Path"); + } + } + else + { + Key.SetValue("SBL3Path", SBL3Path); + } + } + + if (ProfileFFUPath == null) + { + if (Key.GetValue("ProfileFFUPath") != null) + { + Key.DeleteValue("ProfileFFUPath"); + } + } + else + { + Key.SetValue("ProfileFFUPath", ProfileFFUPath); + + App.Config.AddFfuToRepository(ProfileFFUPath); + } + + if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Download && PhoneNotifier.CurrentInterface != PhoneInterfaces.Qualcomm_Flash) + { + NokiaFlashModel Model = (NokiaFlashModel)PhoneNotifier.CurrentModel; + PhoneInfo Info = Model.ReadPhoneInfo(); + + if (EDEPath == null) + { + if (Key.GetValue("EDEPath") != null) + { + Key.DeleteValue("EDEPath"); + } + } + else + { + Key.SetValue("EDEPath", EDEPath); + + App.Config.AddEmergencyToRepository(Info.Type, EDEPath, null); + } + } + + if (SupportedFFUPath != null) + { + Key.SetValue("SupportedFFUPath", SupportedFFUPath); + + App.Config.AddFfuToRepository(SupportedFFUPath); + } + + Key.Close(); + } + + private void Exit() + { + IsSwitchingInterface = false; // From here on a device will be forced to Flash mode again on this screen which is meant for flashing + + FFUPath = null; + LoadersPath = null; + SBL3Path = null; + + Callback(); + ActivateSubContext(null); + } + + internal void ExitMessage(string Message, string SubMessage) + { + // SecureBoot Unlock v2 is done. Reactivate phone arrival events. + MessageViewModel SuccessMessageViewModel = new(Message, () => + { + State = MachineState.Default; + Exit(); + }); + SuccessMessageViewModel.SubMessage = SubMessage; + ActivateSubContext(SuccessMessageViewModel); + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + // Do not start on a new thread, because EvaluateViewState will also create new ViewModels and those should be created on the UI thread. + EvaluateViewState(); + } + } + + internal class BootUnlockResourcesViewModel : FlashResourcesViewModel + { + internal BootUnlockResourcesViewModel(string CurrentMode, byte[] RootKeyHash, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, Action Result, Action Abort, bool IsBootLoaderUnlocked, bool TargetHasNewFlashProtocol, string PlatformID = null, string ProductType = null) + : base(CurrentMode, RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, Result, Abort, IsBootLoaderUnlocked, TargetHasNewFlashProtocol, PlatformID, ProductType) + { + SBL3Path = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SBL3Path", null); + } + } + + internal class BootRestoreResourcesViewModel : FlashResourcesViewModel + { + internal BootRestoreResourcesViewModel(string CurrentMode, byte[] RootKeyHash, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, Action Result, Action Abort, bool IsBootLoaderUnlocked, bool TargetHasNewFlashProtocol, string PlatformID = null, string ProductType = null) + : base(CurrentMode, RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, Result, Abort, IsBootLoaderUnlocked, TargetHasNewFlashProtocol, PlatformID, ProductType) + { + SBL3Path = null; + } + } + + internal class FlashResourcesViewModel : ContextViewModel + { + internal Action SwitchToFlashRom; + internal Action SwitchToUndoRoot; + internal Action SwitchToDownload; + private readonly string PlatformID; + private readonly string ProductType; + private string ValidatedSupportedFfuPath = null; + + internal FlashResourcesViewModel(string CurrentMode, byte[] RootKeyHash, Action SwitchToFlashRom, Action SwitchToUndoRoot, Action SwitchToDownload, Action Result, Action Abort, bool IsBootLoaderUnlocked, bool TargetHasNewFlashProtocol, string PlatformID = null, string ProductType = null) : base() + { + IsSwitchingInterface = true; + this.CurrentMode = CurrentMode; + this.RootKeyHash = RootKeyHash; + this.PlatformID = PlatformID; + this.ProductType = ProductType; + this.SwitchToFlashRom = SwitchToFlashRom; + this.SwitchToUndoRoot = SwitchToUndoRoot; + this.SwitchToDownload = SwitchToDownload; + this.IsBootLoaderUnlocked = IsBootLoaderUnlocked; + OkCommand = new DelegateCommand(() => Result(FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, IsSupportedFfuNeeded ? SupportedFFUPath : null, false), + () => (!TargetHasNewFlashProtocol && (!IsSupportedFfuNeeded || (IsSupportedFfuValid && (SupportedFFUPath != null)))) || ((ProfileFFUPath != null) && (!IsSupportedFfuNeeded || (IsSupportedFfuValid && (SupportedFFUPath != null))))); + FixCommand = new DelegateCommand(() => Result(null, null, null, null, null, null, true)); + CancelCommand = new DelegateCommand(Abort); + this.TargetHasNewFlashProtocol = TargetHasNewFlashProtocol; + + if (TargetHasNewFlashProtocol) + { + SetProfileFFUPath(); + SetEDEPath(); + } + else + { + FFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "FFUPath", null); + SupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); + LoadersPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "LoadersPath", null); + } + } + + private void SetProfileFFUPath() + { + FFU ProfileFFU; + + try + { + if (_ProfileFFUPath != null) + { + ProfileFFU = new FFU(_ProfileFFUPath); + if (PlatformID.StartsWith(ProfileFFU.PlatformID, StringComparison.OrdinalIgnoreCase)) + { + IsProfileFfuValid = true; + return; + } + } + } + catch { } + + try + { + string TempProfileFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "ProfileFFUPath", null); + if (TempProfileFFUPath != null) + { + ProfileFFU = new FFU(TempProfileFFUPath); + if (PlatformID.StartsWith(ProfileFFU.PlatformID, StringComparison.OrdinalIgnoreCase)) + { + ProfileFFUPath = TempProfileFFUPath; + IsProfileFfuValid = true; + return; + } + } + } + catch { } + + List FFUs = App.Config.FFURepository.Where(e => PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); + if (FFUs.Count > 0) + { + IsProfileFfuValid = true; + ProfileFFUPath = FFUs[0].Path; + } + else + { + IsProfileFfuValid = false; + } + } + + private void SetEDEPath() + { + QualcommPartition Programmer; + string TempEDEPath; + + try + { + if (_EDEPath != null) + { + Programmer = new QualcommPartition(_EDEPath); + if (ByteOperations.Compare(Programmer.RootKeyHash, RootKeyHash)) + { + return; + } + } + } + catch { } + + try + { + TempEDEPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "EDEPath", null); + if (TempEDEPath != null) + { + Programmer = new QualcommPartition(TempEDEPath); + if (ByteOperations.Compare(Programmer.RootKeyHash, RootKeyHash)) + { + EDEPath = TempEDEPath; + return; + } + } + } + catch { } + + TempEDEPath = LumiaV2UnlockBootViewModel.GetProgrammerPath(RootKeyHash, ProductType); + if (TempEDEPath != null) + { + Programmer = new QualcommPartition(TempEDEPath); + if (ByteOperations.Compare(Programmer.RootKeyHash, RootKeyHash)) + { + EDEPath = TempEDEPath; + return; + } + } + } + + private void SetSupportedFFUPath() + { + if (!TargetHasNewFlashProtocol) + { + if (this is BootRestoreResourcesViewModel) + { + IsSupportedFfuNeeded = false; + return; + } + + try + { + FFU FFU = new(_FFUPath); + IsSupportedFfuNeeded = !App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == FFU.GetOSVersion()); + } + catch + { + IsSupportedFfuNeeded = false; + return; + } + + if (IsSupportedFfuNeeded) + { + FFU SupportedFFU; + + try + { + if (_SupportedFFUPath != null) + { + SupportedFFU = new FFU(_SupportedFFUPath); + if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) + { + return; + } + } + } + catch { } + + try + { + string TempSupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); + if (TempSupportedFFUPath != null) + { + SupportedFFU = new FFU(TempSupportedFFUPath); + if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) + { + ValidatedSupportedFfuPath = TempSupportedFFUPath; + SupportedFFUPath = TempSupportedFFUPath; + IsSupportedFfuValid = true; + return; + } + } + } + catch { } + + List FFUs = App.Config.FFURepository.Where(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion)).ToList(); + if (FFUs.Count > 0) + { + ValidatedSupportedFfuPath = FFUs[0].Path; + SupportedFFUPath = FFUs[0].Path; + IsSupportedFfuValid = true; + } + } + } + else + { + if ((this is BootRestoreResourcesViewModel) || (_ProfileFFUPath == null)) + { + IsSupportedFfuNeeded = false; + return; + } + + try + { + FFU ProfileFFU = new(_ProfileFFUPath); + IsSupportedFfuNeeded = !App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == ProfileFFU.GetOSVersion()); + } + catch + { + IsSupportedFfuNeeded = false; + return; + } + + if (IsSupportedFfuNeeded) + { + FFU SupportedFFU; + + try + { + if (_SupportedFFUPath != null) + { + SupportedFFU = new FFU(_SupportedFFUPath); + if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) + { + return; + } + } + } + catch { } + + try + { + string TempSupportedFFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SupportedFFUPath", null); + if (TempSupportedFFUPath != null) + { + SupportedFFU = new FFU(TempSupportedFFUPath); + if (App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) + { + ValidatedSupportedFfuPath = TempSupportedFFUPath; + SupportedFFUPath = TempSupportedFFUPath; + IsSupportedFfuValid = true; + return; + } + } + } + catch { } + + List FFUs = App.Config.FFURepository.Where(e => App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion)).ToList(); + if (FFUs.Count > 0) + { + ValidatedSupportedFfuPath = FFUs[0].Path; + SupportedFFUPath = FFUs[0].Path; + IsSupportedFfuValid = true; + } + } + } + } + + private void ValidateSupportedFfuPath() + { + try + { + if (IsSupportedFfuNeeded) + { + if (SupportedFFUPath == null) + { + IsSupportedFfuValid = true; // No visible warning when there is no SupportedFFU selected yet. + } + else + { + if (!TargetHasNewFlashProtocol) + { + if (App.Config.FFURepository.Any(e => (e.Path == SupportedFFUPath) && App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + IsSupportedFfuValid = true; + } + else + { + FFU SupportedFFU = new(SupportedFFUPath); + IsSupportedFfuValid = App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V1.1-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion()); + } + } + else + { + if (App.Config.FFURepository.Any(e => (e.Path == SupportedFFUPath) && App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == e.OSVersion))) + { + IsSupportedFfuValid = true; + } + else + { + FFU SupportedFFU = new(SupportedFFUPath); + IsSupportedFfuValid = App.PatchEngine.PatchDefinitions.First(p => p.Name == "SecureBootHack-V2-EFIESP").TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion()); + } + } + } + } + else + { + IsSupportedFfuValid = true; + } + + if (IsSupportedFfuValid && (SupportedFFUPath != null)) + { + ValidatedSupportedFfuPath = SupportedFFUPath; + } + } + catch + { + IsSupportedFfuValid = false; + } + } + + private bool _IsSupportedFfuNeeded = false; + public bool IsSupportedFfuNeeded + { + get + { + return _IsSupportedFfuNeeded; + } + set + { + _IsSupportedFfuNeeded = value; + OnPropertyChanged(nameof(IsSupportedFfuNeeded)); + OkCommand.RaiseCanExecuteChanged(); + } + } + + private bool _IsSupportedFfuValid = true; + public bool IsSupportedFfuValid + { + get + { + return _IsSupportedFfuValid; + } + set + { + _IsSupportedFfuValid = value; + OnPropertyChanged(nameof(IsSupportedFfuValid)); + OkCommand.RaiseCanExecuteChanged(); + } + } + + private bool _IsProfileFfuValid = true; + public bool IsProfileFfuValid + { + get + { + return _IsProfileFfuValid; + } + set + { + _IsProfileFfuValid = value; + OnPropertyChanged(nameof(IsProfileFfuValid)); + } + } + + private bool _TargetHasNewFlashProtocol = false; + public bool TargetHasNewFlashProtocol + { + get + { + return _TargetHasNewFlashProtocol; + } + set + { + _TargetHasNewFlashProtocol = value; + OnPropertyChanged(nameof(TargetHasNewFlashProtocol)); + OkCommand.RaiseCanExecuteChanged(); + } + } + + private bool _IsBootLoaderUnlocked = false; + public bool IsBootLoaderUnlocked + { + get + { + return _IsBootLoaderUnlocked; + } + set + { + _IsBootLoaderUnlocked = value; + OnPropertyChanged(nameof(IsBootLoaderUnlocked)); + } + } + + private string _FFUPath = null; + public string FFUPath + { + get + { + return _FFUPath; + } + set + { + _FFUPath = value; + OnPropertyChanged(nameof(FFUPath)); + SetSupportedFFUPath(); + OkCommand.RaiseCanExecuteChanged(); + } + } + + private string _ProfileFFUPath = null; + public string ProfileFFUPath + { + get + { + return _ProfileFFUPath; + } + set + { + if (_ProfileFFUPath != value) + { + _ProfileFFUPath = value; + OnPropertyChanged(nameof(ProfileFFUPath)); + SetSupportedFFUPath(); + OkCommand.RaiseCanExecuteChanged(); + } + } + } + + private string _SupportedFFUPath = null; + public string SupportedFFUPath + { + get + { + return _SupportedFFUPath; + } + set + { + if (_SupportedFFUPath != value) + { + _SupportedFFUPath = value; + OnPropertyChanged(nameof(SupportedFFUPath)); + + if (value != ValidatedSupportedFfuPath) + { + ValidateSupportedFfuPath(); + } + } + } + } + + private string _LoadersPath = null; + public string LoadersPath + { + get + { + return _LoadersPath; + } + set + { + _LoadersPath = value; + OnPropertyChanged(nameof(LoadersPath)); + } + } + + private string _EDEPath = null; + public string EDEPath + { + get + { + return _EDEPath; + } + set + { + _EDEPath = value; + OnPropertyChanged(nameof(EDEPath)); + } + } + + private string _SBL3Path = null; + public string SBL3Path + { + get + { + return _SBL3Path; + } + set + { + _SBL3Path = value; + OnPropertyChanged(nameof(SBL3Path)); + } + } + + private string _CurrentMode = null; + public string CurrentMode + { + get + { + return _CurrentMode; + } + set + { + _CurrentMode = value; + OnPropertyChanged(nameof(CurrentMode)); + } + } + + private byte[] _RootKeyHash = null; + public byte[] RootKeyHash + { + get + { + return _RootKeyHash; + } + set + { + _RootKeyHash = value; + OnPropertyChanged(nameof(RootKeyHash)); + } + } + public DelegateCommand OkCommand { get; } = null; + public DelegateCommand CancelCommand { get; } = null; + public DelegateCommand FixCommand { get; } = null; + } +} diff --git a/ViewModels/LumiaUnlockBootloaderViewModel.cs b/WPinternals/ViewModels/LumiaUnlockBootloaderViewModel.cs similarity index 100% rename from ViewModels/LumiaUnlockBootloaderViewModel.cs rename to WPinternals/ViewModels/LumiaUnlockBootloaderViewModel.cs diff --git a/ViewModels/LumiaUnlockRootTargetSelectionViewModel.cs b/WPinternals/ViewModels/LumiaUnlockRootTargetSelectionViewModel.cs similarity index 97% rename from ViewModels/LumiaUnlockRootTargetSelectionViewModel.cs rename to WPinternals/ViewModels/LumiaUnlockRootTargetSelectionViewModel.cs index 868af9a..f3fae6f 100644 --- a/ViewModels/LumiaUnlockRootTargetSelectionViewModel.cs +++ b/WPinternals/ViewModels/LumiaUnlockRootTargetSelectionViewModel.cs @@ -1,190 +1,190 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - internal class LumiaUnlockRootTargetSelectionViewModel : LumiaRootAccessTargetSelectionViewModel - { - public LumiaUnlockRootTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, Action UnlockPhoneCallback, Action UnlockImageCallback) - : base(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, UnlockPhoneCallback, UnlockImageCallback) { } - } - - internal class LumiaUndoRootTargetSelectionViewModel : LumiaRootAccessTargetSelectionViewModel - { - public LumiaUndoRootTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, Action UnlockPhoneCallback, Action UnlockImageCallback) - : base(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, UnlockPhoneCallback, UnlockImageCallback) { } - } - - internal class LumiaRootAccessTargetSelectionViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - internal Action SwitchToUnlockBoot; - internal Action SwitchToDumpRom; - internal Action SwitchToFlashRom; - private readonly Action UnlockPhoneCallback; - private readonly Action UnlockImageCallback; - - internal LumiaRootAccessTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, Action UnlockPhoneCallback, Action UnlockImageCallback) - : base() - { - this.PhoneNotifier = PhoneNotifier; - this.SwitchToDumpRom = SwitchToDumpRom; - this.SwitchToFlashRom = SwitchToFlashRom; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.UnlockPhoneCallback = UnlockPhoneCallback; - this.UnlockImageCallback = UnlockImageCallback; - - this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - this.PhoneNotifier.DeviceRemoved += DeviceRemoved; - - new Thread(() => EvaluateViewState()).Start(); - } - - private string _EFIESPMountPoint; - public string EFIESPMountPoint - { - get - { - return _EFIESPMountPoint; - } - set - { - if (value != _EFIESPMountPoint) - { - _EFIESPMountPoint = value; - OnPropertyChanged(nameof(EFIESPMountPoint)); - } - } - } - - private string _MainOSMountPoint; - public string MainOSMountPoint - { - get - { - return _MainOSMountPoint; - } - set - { - if (value != _MainOSMountPoint) - { - _MainOSMountPoint = value; - OnPropertyChanged(nameof(MainOSMountPoint)); - } - } - } - - private bool _IsPhoneDisconnected; - public bool IsPhoneDisconnected - { - get - { - return _IsPhoneDisconnected; - } - set - { - if (value != _IsPhoneDisconnected) - { - _IsPhoneDisconnected = value; - OnPropertyChanged(nameof(IsPhoneDisconnected)); - } - } - } - - private bool _IsPhoneInMassStorage; - public bool IsPhoneInMassStorage - { - get - { - return _IsPhoneInMassStorage; - } - set - { - if (value != _IsPhoneInMassStorage) - { - _IsPhoneInMassStorage = value; - OnPropertyChanged(nameof(IsPhoneInMassStorage)); - } - } - } - - private bool _IsPhoneInOtherMode; - public bool IsPhoneInOtherMode - { - get - { - return _IsPhoneInOtherMode; - } - set - { - if (value != _IsPhoneInOtherMode) - { - _IsPhoneInOtherMode = value; - OnPropertyChanged(nameof(IsPhoneInOtherMode)); - } - } - } - - private DelegateCommand _UnlockPhoneCommand; - public DelegateCommand UnlockPhoneCommand - { - get - { - return _UnlockPhoneCommand ??= new DelegateCommand(() => UnlockPhoneCallback(), () => !IsPhoneDisconnected); - } - } - - private DelegateCommand _UnlockImageCommand; - public DelegateCommand UnlockImageCommand - { - get - { - return _UnlockImageCommand ??= new DelegateCommand(() => UnlockImageCallback(EFIESPMountPoint, MainOSMountPoint), () => (EFIESPMountPoint != null) || (MainOSMountPoint != null)); - } - } - - ~LumiaRootAccessTargetSelectionViewModel() - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - new Thread(() => EvaluateViewState()).Start(); - } - - private void DeviceRemoved() - { - new Thread(() => EvaluateViewState()).Start(); - } - - internal override void EvaluateViewState() - { - IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; - IsPhoneInMassStorage = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_MassStorage; - IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInMassStorage; - UnlockPhoneCommand.RaiseCanExecuteChanged(); - UnlockImageCommand.RaiseCanExecuteChanged(); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + internal class LumiaUnlockRootTargetSelectionViewModel : LumiaRootAccessTargetSelectionViewModel + { + public LumiaUnlockRootTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, Action UnlockPhoneCallback, Action UnlockImageCallback) + : base(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, UnlockPhoneCallback, UnlockImageCallback) { } + } + + internal class LumiaUndoRootTargetSelectionViewModel : LumiaRootAccessTargetSelectionViewModel + { + public LumiaUndoRootTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, Action UnlockPhoneCallback, Action UnlockImageCallback) + : base(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, UnlockPhoneCallback, UnlockImageCallback) { } + } + + internal class LumiaRootAccessTargetSelectionViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + internal Action SwitchToUnlockBoot; + internal Action SwitchToDumpRom; + internal Action SwitchToFlashRom; + private readonly Action UnlockPhoneCallback; + private readonly Action UnlockImageCallback; + + internal LumiaRootAccessTargetSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, Action UnlockPhoneCallback, Action UnlockImageCallback) + : base() + { + this.PhoneNotifier = PhoneNotifier; + this.SwitchToDumpRom = SwitchToDumpRom; + this.SwitchToFlashRom = SwitchToFlashRom; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.UnlockPhoneCallback = UnlockPhoneCallback; + this.UnlockImageCallback = UnlockImageCallback; + + this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + this.PhoneNotifier.DeviceRemoved += DeviceRemoved; + + new Thread(() => EvaluateViewState()).Start(); + } + + private string _EFIESPMountPoint; + public string EFIESPMountPoint + { + get + { + return _EFIESPMountPoint; + } + set + { + if (value != _EFIESPMountPoint) + { + _EFIESPMountPoint = value; + OnPropertyChanged(nameof(EFIESPMountPoint)); + } + } + } + + private string _MainOSMountPoint; + public string MainOSMountPoint + { + get + { + return _MainOSMountPoint; + } + set + { + if (value != _MainOSMountPoint) + { + _MainOSMountPoint = value; + OnPropertyChanged(nameof(MainOSMountPoint)); + } + } + } + + private bool _IsPhoneDisconnected; + public bool IsPhoneDisconnected + { + get + { + return _IsPhoneDisconnected; + } + set + { + if (value != _IsPhoneDisconnected) + { + _IsPhoneDisconnected = value; + OnPropertyChanged(nameof(IsPhoneDisconnected)); + } + } + } + + private bool _IsPhoneInMassStorage; + public bool IsPhoneInMassStorage + { + get + { + return _IsPhoneInMassStorage; + } + set + { + if (value != _IsPhoneInMassStorage) + { + _IsPhoneInMassStorage = value; + OnPropertyChanged(nameof(IsPhoneInMassStorage)); + } + } + } + + private bool _IsPhoneInOtherMode; + public bool IsPhoneInOtherMode + { + get + { + return _IsPhoneInOtherMode; + } + set + { + if (value != _IsPhoneInOtherMode) + { + _IsPhoneInOtherMode = value; + OnPropertyChanged(nameof(IsPhoneInOtherMode)); + } + } + } + + private DelegateCommand _UnlockPhoneCommand; + public DelegateCommand UnlockPhoneCommand + { + get + { + return _UnlockPhoneCommand ??= new DelegateCommand(() => UnlockPhoneCallback(), () => !IsPhoneDisconnected); + } + } + + private DelegateCommand _UnlockImageCommand; + public DelegateCommand UnlockImageCommand + { + get + { + return _UnlockImageCommand ??= new DelegateCommand(() => UnlockImageCallback(EFIESPMountPoint, MainOSMountPoint), () => (EFIESPMountPoint != null) || (MainOSMountPoint != null)); + } + } + + ~LumiaRootAccessTargetSelectionViewModel() + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + new Thread(() => EvaluateViewState()).Start(); + } + + private void DeviceRemoved() + { + new Thread(() => EvaluateViewState()).Start(); + } + + internal override void EvaluateViewState() + { + IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; + IsPhoneInMassStorage = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_MassStorage; + IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInMassStorage; + UnlockPhoneCommand.RaiseCanExecuteChanged(); + UnlockImageCommand.RaiseCanExecuteChanged(); + } + } +} diff --git a/ViewModels/LumiaUnlockRootViewModel.cs b/WPinternals/ViewModels/LumiaUnlockRootViewModel.cs similarity index 97% rename from ViewModels/LumiaUnlockRootViewModel.cs rename to WPinternals/ViewModels/LumiaUnlockRootViewModel.cs index f62b97b..082d434 100644 --- a/ViewModels/LumiaUnlockRootViewModel.cs +++ b/WPinternals/ViewModels/LumiaUnlockRootViewModel.cs @@ -1,290 +1,290 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Linq; -using System.Threading; - -namespace WPinternals -{ - internal class LumiaUnlockRootViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - private readonly Action SwitchToUnlockBoot; - private readonly Action SwitchToDumpRom; - private readonly Action SwitchToFlashRom; - private readonly Action Callback; - private readonly bool DoUnlock; - - internal LumiaUnlockRootViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, bool DoUnlock, Action Callback) - : base() - { - IsSwitchingInterface = false; - IsFlashModeOperation = true; - - this.PhoneNotifier = PhoneNotifier; - this.SwitchToDumpRom = SwitchToDumpRom; - this.SwitchToFlashRom = SwitchToFlashRom; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.DoUnlock = DoUnlock; - this.Callback = Callback; - } - - internal override void EvaluateViewState() - { - if (!IsActive) - { - return; - } - - if (DoUnlock) - { - if ((SubContextViewModel == null) || (SubContextViewModel is LumiaUndoRootTargetSelectionViewModel)) - { - ActivateSubContext(new LumiaUnlockRootTargetSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, DoUnlockPhone, DoUnlockImage)); - } - } - else - { - if ((SubContextViewModel == null) || (SubContextViewModel is LumiaUnlockRootTargetSelectionViewModel)) - { - ActivateSubContext(new LumiaUndoRootTargetSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, DoUnlockPhone, DoUnlockImage)); - } - } - } - - internal async void DoUnlockPhone() - { - try - { - IsSwitchingInterface = true; - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - bool HasNewBootloader = HasNewBootloaderFromMassStorage(); - string EFIESPPath = HasNewBootloader ? null : ((MassStorage)PhoneNotifier.CurrentModel).Drive + @"\EFIESP\"; - string MainOSPath = ((MassStorage)PhoneNotifier.CurrentModel).Drive + @"\"; - - bool HasV11Patches = HasV11PatchesFromMassStorage(); - - StartPatch(EFIESPPath, MainOSPath, HasNewBootloader, HasV11Patches); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, () => - { - Callback(); - ActivateSubContext(null); - })); - } - } - - internal void DoUnlockImage(string EFIESPMountPoint, string MainOSMountPoint) - { - StartPatch(EFIESPMountPoint, MainOSMountPoint, false, false); // Unlock image is only supported for Lumia's with bootloader Spec A. Due to complexity of Spec B bootloader hack, it cannot be applied on a mounted image. - } - - // Magic! - // Apply patches for Root Access - private void StartPatch(string EFIESP, string MainOS, bool HasNewBootloader, bool HasV11Patches) - { - IsSwitchingInterface = false; - new Thread(() => - { - if (DoUnlock) - { - LogFile.BeginAction("EnableRootAccess"); - } - else - { - LogFile.BeginAction("DisableRootAccess"); - } - - bool Result = false; - - if (EFIESP != null && !HasV11Patches) - { - if (DoUnlock) - { - ActivateSubContext(new BusyViewModel("Enable Root Access on EFIESP...")); - } - else - { - ActivateSubContext(new BusyViewModel("Disable Root Access on EFIESP...")); - } - - try - { - App.PatchEngine.TargetPath = EFIESP; - if (DoUnlock) - { - Result = App.PatchEngine.Patch("SecureBootHack-V1-EFIESP"); - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to enable Root Access on EFIESP! Check the OS version on the phone and verify the compatibility-list in the \"Getting started\" section.", Exit)); - return; - } - } - else - { - App.PatchEngine.Restore("SecureBootHack-V1-EFIESP"); - Result = true; - } - } - catch (UnauthorizedAccessException Ex) - { - LogFile.LogException(Ex); - ActivateSubContext(new MessageViewModel("Failed to enable Root Access on EFIESP! Not enough privileges to perform action. Try to logon to Windows with an administrator account.", Exit)); - return; - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to enable Root Access on EFIESP!", Exit)); - return; - } - } - - if (MainOS != null) - { - if (DoUnlock) - { - ActivateSubContext(new BusyViewModel("Enable Root Access on MainOS...")); - } - else - { - ActivateSubContext(new BusyViewModel("Disable Root Access on MainOS...")); - } - - try - { - App.PatchEngine.TargetPath = MainOS; - if (DoUnlock) - { - Result = App.PatchEngine.Patch("RootAccess-MainOS"); - - if (Result) - { - Result = App.PatchEngine.Patch("SecureBootHack-MainOS"); - } - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to enable Root Access on MainOS! Check the OS version on the phone and verify the compatibility-list in the \"Getting started\" section.", Exit)); - return; - } - } - else - { - App.PatchEngine.Restore("RootAccess-MainOS"); - - if (!HasNewBootloader) - { - App.PatchEngine.Restore("SecureBootHack-MainOS"); - } - - Result = true; - } - } - catch (UnauthorizedAccessException Ex) - { - LogFile.LogException(Ex); - ActivateSubContext(new MessageViewModel("Failed to enable Root Access on MainOS! Not enough privileges to perform action. Try to logon to Windows with an administrator account.", Exit)); - Result = false; - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to enable Root Access on MainOS!", Exit)); - } - else - { - if (DoUnlock) - { - ActivateSubContext(new MessageViewModel("Root Access successfully enabled!", Exit)); - } - else - { - ActivateSubContext(new MessageViewModel("Root Access successfully disabled!", Exit)); - } - } - } - - if (DoUnlock) - { - LogFile.EndAction("EnableRootAccess"); - } - else - { - LogFile.EndAction("DisableRootAccess"); - } - }).Start(); - } - - private void Exit() - { - IsSwitchingInterface = false; - Callback(); - ActivateSubContext(null); - } - - private bool HasNewBootloaderFromMassStorage() - { - bool Result = false; - MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; - Phone.OpenVolume(false); - byte[] GPTBuffer = Phone.ReadSectors(1, 33); - GPT GPT = new(GPTBuffer); - Partition Partition = GPT.GetPartition("UEFI"); - byte[] UefiBuffer = Phone.ReadSectors(Partition.FirstSector, Partition.LastSector - Partition.FirstSector + 1); - UEFI UEFI = new(UefiBuffer); - string BootMgrName = UEFI.EFIs.First(efi => (efi.Name != null) && (efi.Name.Contains("BootMgrApp") || efi.Name.Contains("FlashApp"))).Name; - byte[] BootMgr = UEFI.GetFile(BootMgrName); - // "Header V2" - Result = ByteOperations.FindAscii(BootMgr, "Header V2") != null; - Phone.CloseVolume(); - return Result; - } - - private bool HasV11PatchesFromMassStorage() - { - bool Result = false; - MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; - Phone.OpenVolume(false); - byte[] GPTBuffer = Phone.ReadSectors(1, 33); - GPT GPT = new(GPTBuffer); - Partition Partition = GPT.GetPartition("BACKUP_BS_NV"); - Result = Partition != null; - Phone.CloseVolume(); - return Result; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Threading; + +namespace WPinternals +{ + internal class LumiaUnlockRootViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + private readonly Action SwitchToUnlockBoot; + private readonly Action SwitchToDumpRom; + private readonly Action SwitchToFlashRom; + private readonly Action Callback; + private readonly bool DoUnlock; + + internal LumiaUnlockRootViewModel(PhoneNotifierViewModel PhoneNotifier, Action SwitchToUnlockBoot, Action SwitchToDumpRom, Action SwitchToFlashRom, bool DoUnlock, Action Callback) + : base() + { + IsSwitchingInterface = false; + IsFlashModeOperation = true; + + this.PhoneNotifier = PhoneNotifier; + this.SwitchToDumpRom = SwitchToDumpRom; + this.SwitchToFlashRom = SwitchToFlashRom; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.DoUnlock = DoUnlock; + this.Callback = Callback; + } + + internal override void EvaluateViewState() + { + if (!IsActive) + { + return; + } + + if (DoUnlock) + { + if ((SubContextViewModel == null) || (SubContextViewModel is LumiaUndoRootTargetSelectionViewModel)) + { + ActivateSubContext(new LumiaUnlockRootTargetSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, DoUnlockPhone, DoUnlockImage)); + } + } + else + { + if ((SubContextViewModel == null) || (SubContextViewModel is LumiaUnlockRootTargetSelectionViewModel)) + { + ActivateSubContext(new LumiaUndoRootTargetSelectionViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpRom, SwitchToFlashRom, DoUnlockPhone, DoUnlockImage)); + } + } + } + + internal async void DoUnlockPhone() + { + try + { + IsSwitchingInterface = true; + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_MassStorage, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + bool HasNewBootloader = HasNewBootloaderFromMassStorage(); + string EFIESPPath = HasNewBootloader ? null : ((MassStorage)PhoneNotifier.CurrentModel).Drive + @"\EFIESP\"; + string MainOSPath = ((MassStorage)PhoneNotifier.CurrentModel).Drive + @"\"; + + bool HasV11Patches = HasV11PatchesFromMassStorage(); + + StartPatch(EFIESPPath, MainOSPath, HasNewBootloader, HasV11Patches); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, () => + { + Callback(); + ActivateSubContext(null); + })); + } + } + + internal void DoUnlockImage(string EFIESPMountPoint, string MainOSMountPoint) + { + StartPatch(EFIESPMountPoint, MainOSMountPoint, false, false); // Unlock image is only supported for Lumia's with bootloader Spec A. Due to complexity of Spec B bootloader hack, it cannot be applied on a mounted image. + } + + // Magic! + // Apply patches for Root Access + private void StartPatch(string EFIESP, string MainOS, bool HasNewBootloader, bool HasV11Patches) + { + IsSwitchingInterface = false; + new Thread(() => + { + if (DoUnlock) + { + LogFile.BeginAction("EnableRootAccess"); + } + else + { + LogFile.BeginAction("DisableRootAccess"); + } + + bool Result = false; + + if (EFIESP != null && !HasV11Patches) + { + if (DoUnlock) + { + ActivateSubContext(new BusyViewModel("Enable Root Access on EFIESP...")); + } + else + { + ActivateSubContext(new BusyViewModel("Disable Root Access on EFIESP...")); + } + + try + { + App.PatchEngine.TargetPath = EFIESP; + if (DoUnlock) + { + Result = App.PatchEngine.Patch("SecureBootHack-V1-EFIESP"); + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to enable Root Access on EFIESP! Check the OS version on the phone and verify the compatibility-list in the \"Getting started\" section.", Exit)); + return; + } + } + else + { + App.PatchEngine.Restore("SecureBootHack-V1-EFIESP"); + Result = true; + } + } + catch (UnauthorizedAccessException Ex) + { + LogFile.LogException(Ex); + ActivateSubContext(new MessageViewModel("Failed to enable Root Access on EFIESP! Not enough privileges to perform action. Try to logon to Windows with an administrator account.", Exit)); + return; + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to enable Root Access on EFIESP!", Exit)); + return; + } + } + + if (MainOS != null) + { + if (DoUnlock) + { + ActivateSubContext(new BusyViewModel("Enable Root Access on MainOS...")); + } + else + { + ActivateSubContext(new BusyViewModel("Disable Root Access on MainOS...")); + } + + try + { + App.PatchEngine.TargetPath = MainOS; + if (DoUnlock) + { + Result = App.PatchEngine.Patch("RootAccess-MainOS"); + + if (Result) + { + Result = App.PatchEngine.Patch("SecureBootHack-MainOS"); + } + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to enable Root Access on MainOS! Check the OS version on the phone and verify the compatibility-list in the \"Getting started\" section.", Exit)); + return; + } + } + else + { + App.PatchEngine.Restore("RootAccess-MainOS"); + + if (!HasNewBootloader) + { + App.PatchEngine.Restore("SecureBootHack-MainOS"); + } + + Result = true; + } + } + catch (UnauthorizedAccessException Ex) + { + LogFile.LogException(Ex); + ActivateSubContext(new MessageViewModel("Failed to enable Root Access on MainOS! Not enough privileges to perform action. Try to logon to Windows with an administrator account.", Exit)); + Result = false; + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to enable Root Access on MainOS!", Exit)); + } + else + { + if (DoUnlock) + { + ActivateSubContext(new MessageViewModel("Root Access successfully enabled!", Exit)); + } + else + { + ActivateSubContext(new MessageViewModel("Root Access successfully disabled!", Exit)); + } + } + } + + if (DoUnlock) + { + LogFile.EndAction("EnableRootAccess"); + } + else + { + LogFile.EndAction("DisableRootAccess"); + } + }).Start(); + } + + private void Exit() + { + IsSwitchingInterface = false; + Callback(); + ActivateSubContext(null); + } + + private bool HasNewBootloaderFromMassStorage() + { + bool Result = false; + MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; + Phone.OpenVolume(false); + byte[] GPTBuffer = Phone.ReadSectors(1, 33); + GPT GPT = new(GPTBuffer); + Partition Partition = GPT.GetPartition("UEFI"); + byte[] UefiBuffer = Phone.ReadSectors(Partition.FirstSector, Partition.LastSector - Partition.FirstSector + 1); + UEFI UEFI = new(UefiBuffer); + string BootMgrName = UEFI.EFIs.First(efi => (efi.Name != null) && (efi.Name.Contains("BootMgrApp") || efi.Name.Contains("FlashApp"))).Name; + byte[] BootMgr = UEFI.GetFile(BootMgrName); + // "Header V2" + Result = ByteOperations.FindAscii(BootMgr, "Header V2") != null; + Phone.CloseVolume(); + return Result; + } + + private bool HasV11PatchesFromMassStorage() + { + bool Result = false; + MassStorage Phone = (MassStorage)PhoneNotifier.CurrentModel; + Phone.OpenVolume(false); + byte[] GPTBuffer = Phone.ReadSectors(1, 33); + GPT GPT = new(GPTBuffer); + Partition Partition = GPT.GetPartition("BACKUP_BS_NV"); + Result = Partition != null; + Phone.CloseVolume(); + return Result; + } + } +} diff --git a/ViewModels/LumiaV2UnlockBootViewModel.cs b/WPinternals/ViewModels/LumiaV2UnlockBootViewModel.cs similarity index 98% rename from ViewModels/LumiaV2UnlockBootViewModel.cs rename to WPinternals/ViewModels/LumiaV2UnlockBootViewModel.cs index a6b2cce..a8aea6a 100644 --- a/ViewModels/LumiaV2UnlockBootViewModel.cs +++ b/WPinternals/ViewModels/LumiaV2UnlockBootViewModel.cs @@ -1,3004 +1,3004 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Threading.Tasks; - -namespace WPinternals -{ - internal class LumiaV2UnlockBootViewModel : ContextViewModel - { - internal static async Task LumiaV2FindFlashingProfile(PhoneNotifierViewModel Notifier, string FFUPath, bool DoResetFirst = true, bool Experimental = false, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) - { - if (SetWorkingStatus == null) - { - SetWorkingStatus = (m, s, v, a, st) => { }; - } - - if (UpdateWorkingStatus == null) - { - UpdateWorkingStatus = (m, s, v, st) => { }; - } - - if (ExitSuccess == null) - { - ExitSuccess = (m, s) => { }; - } - - if (ExitFailure == null) - { - ExitFailure = (m, s) => { }; - } - - LogFile.BeginAction("FindFlashingProfile"); - try - { - LogFile.Log("Find Flashing Profile", LogType.FileAndConsole); - - NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - - PhoneInfo Info; - if (DoResetFirst) - { - // The phone will be reset before flashing, so we have the opportunity to get some more info from the phone - Info = FlashModel.ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - - string FfuFirmware = null; - if (FFUPath != null) - { - FFU FFU = new(FFUPath); - FfuFirmware = FFU.GetFirmwareVersion(); - } - FlashProfile Profile = App.Config.GetProfile(Info.PlatformID, Info.Firmware, FfuFirmware); - - if (Profile != null) - { - LogFile.Log("Flashing Profile already present for this phone", LogType.FileAndConsole); - return; - } - } - else - { - Info = FlashModel.ReadPhoneInfo(ExtendedInfo: false); - } - - SetWorkingStatus("Scanning for flashing-profile", "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", null, Status: WPinternalsStatus.Scanning); - - await LumiaV2CustomFlash(Notifier, FFUPath, false, !Info.IsBootloaderSecure, null, DoResetFirst, Experimental: Experimental, SetWorkingStatus: - (m, s, v, a, st) => - { - if (st == WPinternalsStatus.SwitchingMode) - { - SetWorkingStatus(m, s, v, a, st); - } - }, - UpdateWorkingStatus: - (m, s, v, st) => - { - if (st == WPinternalsStatus.SwitchingMode) - { - UpdateWorkingStatus(m, s, v, st); - } - }, - ExitSuccess: ExitSuccess, ExitFailure: ExitFailure); - - LogFile.Log("Flashing profile found!", LogType.FileAndConsole); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("FindFlashingProfile"); - } - } - - internal static async Task LumiaV2EnableTestSigning(System.Threading.SynchronizationContext UIContext, string FFUPath, bool DoResetFirst = true) - { - LogFile.BeginAction("EnableTestSigning"); - try - { - LogFile.Log("Command: Enable testsigning", LogType.FileAndConsole); - PhoneNotifierViewModel Notifier = new(); - UIContext.Send(s => Notifier.Start(), null); - NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - - List Parts = new(); - FlashPart Part; - - // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. - // We need the fist sector if we want to write back the GPT. - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); - GPT GPT = new(GPTChunk); - bool GPTChanged = false; - - Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); - Partition UEFI_BS_NV; - if (BACKUP_BS_NV == null) - { - BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); - Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; - Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; - BACKUP_BS_NV.Name = "BACKUP_BS_NV"; - BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); - BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); - UEFI_BS_NV = new Partition - { - Name = "UEFI_BS_NV", - Attributes = BACKUP_BS_NV.Attributes, - PartitionGuid = OriginalPartitionGuid, - PartitionTypeGuid = OriginalPartitionTypeGuid, - FirstSector = BACKUP_BS_NV.LastSector + 1 - }; - UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; - GPT.Partitions.Add(UEFI_BS_NV); - GPTChanged = true; - } - if (GPTChanged) - { - GPT.Rebuild(); - Part = new FlashPart - { - StartSector = 0, - Stream = new MemoryStream(GPTChunk) - }; - Parts.Add(Part); - } - - // This code was used to compress the partition to an embedded resource: - // - // byte[] sbpart = System.IO.File.ReadAllBytes(@"C:\Windows Phone 8\Sources\WPInternals\SB.Original.bin"); - // System.IO.FileStream s = new System.IO.FileStream(@"C:\Windows Phone 8\Sources\WPInternals\SB", System.IO.FileMode.Create, System.IO.FileAccess.Write); - // CompressedStream Out = new CompressedStream(s, (ulong)sbpart.Length); - // Out.Write(sbpart, 0, sbpart.Length); - // Out.Close(); - // s.Close(); - - Part = new FlashPart(); - Partition TargetPartition = GPT.GetPartition("UEFI_BS_NV"); - Part.StartSector = (UInt32)TargetPartition.FirstSector; // GPT is prepared for 64-bit sector-offset, but flash app isn't. - Part.Stream = new SeekableStream(() => - { - var assembly = System.Reflection.Assembly.GetExecutingAssembly(); - - // Magic! - // The SB resource is a compressed version of a raw NV-variable-partition. - // In this partition the SecureBoot variable is disabled. - // It overwrites the variable in a different NV-partition than where this variable is stored usually. - // This normally leads to endless-loops when the NV-variables are enumerated. - // But the partition contains an extra hack to break out the endless loops. - var stream = assembly.GetManifestResourceStream("WPinternals.SB"); - - return new DecompressedStream(stream); - }); - Parts.Add(Part); - - await LumiaV2CustomFlash(Notifier, FFUPath, false, false, Parts, DoResetFirst, ClearFlashingStatusAtEnd: false); - - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("EnableTestSigning"); - } - } - - internal static async Task LumiaV2SwitchToMassStorageMode(PhoneNotifierViewModel Notifier, string FFUPath, bool DoResetFirst = true) - { - // If there is no phone connected yet, we wait here for the phone to connect. - // Because it could be connecting in mass storage mode. - // So we dont want to let SwitchTo() wait for it, because it might already be trying to switch to flash mode, which is not possible and not necessary at that point. - if (Notifier.CurrentInterface == null) - { - LogFile.Log("Waiting for phone to connect...", LogType.FileAndConsole); - await Notifier.WaitForArrival(); - } - - if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_MassStorage) - { - LogFile.Log("Phone is already in Mass Storage Mode", LogType.FileAndConsole); - return ((MassStorage)Notifier.CurrentModel).Drive; - } - - NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - if (DoResetFirst) - { - // The phone will be reset before flashing, so we have the opportunity to get some more info from the phone - PhoneInfo Info = FlashModel.ReadPhoneInfo(); - Info.Log(LogType.ConsoleOnly); - } - - await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); - - MassStorage Storage = null; - if (Notifier.CurrentModel is MassStorage) - { - Storage = (MassStorage)Notifier.CurrentModel; - } - - if (Storage == null) - { - throw new WPinternalsException("Failed to switch to Mass Storage Mode"); - } - - return Storage.Drive; - } - - internal static async Task LumiaV2ClearNV(System.Threading.SynchronizationContext UIContext, string FFUPath, bool DoResetFirst = true) - { - LogFile.BeginAction("ClearNV"); - try - { - LogFile.Log("Command: Clear NV", LogType.FileAndConsole); - PhoneNotifierViewModel Notifier = new(); - UIContext.Send(s => Notifier.Start(), null); - NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - List Parts = new(); - - // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. - // We need the fist sector if we want to write back the GPT. - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); - GPT GPT = new(GPTChunk); - bool GPTChanged = false; - Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); - Partition UEFI_BS_NV; - if (BACKUP_BS_NV == null) - { - BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); - Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; - Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; - BACKUP_BS_NV.Name = "BACKUP_BS_NV"; - BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); - BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); - UEFI_BS_NV = new Partition - { - Name = "UEFI_BS_NV", - Attributes = BACKUP_BS_NV.Attributes, - PartitionGuid = OriginalPartitionGuid, - PartitionTypeGuid = OriginalPartitionTypeGuid, - FirstSector = BACKUP_BS_NV.LastSector + 1 - }; - UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; - GPT.Partitions.Add(UEFI_BS_NV); - GPTChanged = true; - } - if (GPTChanged) - { - GPT.Rebuild(); - FlashPart Part = new(); - Part.StartSector = 0; - Part.Stream = new MemoryStream(GPTChunk); - Parts.Add(Part); - } - - using (MemoryStream Space = new(new byte[0x40000])) - { - Partition Target = GPT.GetPartition("UEFI_BS_NV"); - Parts.Add(new FlashPart() { StartSector = (uint)Target.FirstSector, Stream = Space }); - - await LumiaV2CustomFlash(Notifier, FFUPath, false, false, Parts, DoResetFirst, ClearFlashingStatusAtEnd: false); - } - - LogFile.Log("NV successfully cleared!", LogType.FileAndConsole); - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("ClearNV"); - } - } - - internal static async Task LumiaV2FlashPartition(System.Threading.SynchronizationContext UIContext, string FFUPath, string PartitionName, string PartitionPath, bool DoResetFirst = true) - { - LogFile.BeginAction("FlashPartition"); - try - { - LogFile.Log("Command: Flash Partition", LogType.FileAndConsole); - LogFile.Log("Partition name: " + PartitionName, LogType.FileAndConsole); - LogFile.Log("Partition file: " + PartitionPath, LogType.FileAndConsole); - if (FFUPath != null) - { - LogFile.Log("Profile FFU file: " + FFUPath, LogType.FileAndConsole); - } - - PhoneNotifierViewModel Notifier = new(); - UIContext.Send(s => Notifier.Start(), null); - - NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - - PhoneInfo Info = FlashModel.ReadPhoneInfo(); - - // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. - // We need the fist sector if we want to write back the GPT. - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); - GPT GPT = new(GPTChunk); - - Partition TargetPartition = GPT.GetPartition(PartitionName); - if (TargetPartition == null) - { - throw new WPinternalsException("Target partition not found!", "Couldn't find \"" + PartitionName + "\" from the device GPT."); - } - - LogFile.Log("Target-partition found at sector: 0x" + TargetPartition.FirstSector.ToString("X8") + " - 0x" + TargetPartition.LastSector.ToString("X8"), LogType.FileAndConsole); - - bool IsUnlocked = false; - bool GPTChanged = false; - List Parts = new(); - FlashPart Part; - if (string.Equals(PartitionName, "EFIESP", StringComparison.CurrentCultureIgnoreCase)) - { - byte[] EfiespBinary = File.ReadAllBytes(PartitionPath); - IsUnlocked = (ByteOperations.ReadUInt32(EfiespBinary, 0x20) == (EfiespBinary.Length / 0x200 / 2)) && ByteOperations.ReadAsciiString(EfiespBinary, (UInt32)(EfiespBinary.Length / 2) + 3, 8) == "MSDOS5.0"; - - if (IsUnlocked) - { - Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); - if (IsUnlockedFlag == null) - { - IsUnlockedFlag = new Partition - { - Name = "IS_UNLOCKED", - Attributes = 0, - PartitionGuid = Guid.NewGuid(), - PartitionTypeGuid = Guid.NewGuid(), - FirstSector = 0x40, - LastSector = 0x40 - }; - GPT.Partitions.Add(IsUnlockedFlag); - GPTChanged = true; - } - } - else - { - Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); - if (IsUnlockedFlag != null) - { - GPT.Partitions.Remove(IsUnlockedFlag); - GPTChanged = true; - } - } - - if (GPTChanged) - { - GPT.Rebuild(); - Part = new FlashPart - { - StartSector = 0, - Stream = new MemoryStream(GPTChunk) - }; - Parts.Add(Part); - } - } - - using (FileStream Stream = new(PartitionPath, FileMode.Open)) - { - if ((UInt64)Stream.Length != (TargetPartition.SizeInSectors * 0x200)) - { - throw new WPinternalsException("Raw partition has wrong size. Size = 0x" + Stream.Length.ToString("X8") + ". Expected size = 0x" + (TargetPartition.SizeInSectors * 0x200).ToString("X8")); - } - - Part = new FlashPart - { - StartSector = (UInt32)TargetPartition.FirstSector, - Stream = Stream - }; - Parts.Add(Part); - await LumiaV2CustomFlash(Notifier, FFUPath, false, false, Parts, DoResetFirst, !string.Equals(PartitionName, "UEFI_BS_NV", StringComparison.CurrentCultureIgnoreCase)); - } - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("FlashPartition"); - } - } - - internal static async Task LumiaV2FlashRaw(System.Threading.SynchronizationContext UIContext, UInt64 StartSector, string DataPath, string FFUPath, bool DoResetFirst = true) - { - LogFile.BeginAction("FlashRaw"); - try - { - LogFile.Log("Command: Flash Raw", LogType.FileAndConsole); - LogFile.Log("Start sector: 0x" + StartSector.ToString("X16"), LogType.FileAndConsole); - LogFile.Log("Data file: " + DataPath, LogType.FileAndConsole); - if (FFUPath != null) - { - LogFile.Log("FFU file: " + FFUPath, LogType.FileAndConsole); - } - - PhoneNotifierViewModel Notifier = new(); - UIContext.Send(s => Notifier.Start(), null); - - NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); - - PhoneInfo Info = FlashModel.ReadPhoneInfo(); - - byte[] Data = File.ReadAllBytes(DataPath); - - await LumiaV2CustomFlash(Notifier, FFUPath, false, false, (UInt32)StartSector, Data, DoResetFirst); - Notifier.Stop(); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - } - finally - { - LogFile.EndAction("FlashRaw"); - } - } - - internal async static Task LumiaV2CustomFlash(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, UInt32 StartSector, byte[] Data, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false) //, string LoaderPath = null) - { - using MemoryStream Stream = new(Data); - FlashPart Part = new() { StartSector = StartSector, Stream = Stream }; - List Parts = new(); - Parts.Add(Part); - await LumiaV2CustomFlash(Notifier, FFUPath, PerformFullFlashFirst, SkipWrite, Parts, DoResetFirst, ClearFlashingStatusAtEnd, CheckSectorAlignment, ShowProgress, Experimental); - } - - internal async static Task LumiaV2CustomFlash(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, UInt32 StartSector, Stream Data, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false) //, string LoaderPath = null) - { - FlashPart Part = new() { StartSector = StartSector, Stream = Data }; - List Parts = new(); - Parts.Add(Part); - await LumiaV2CustomFlash(Notifier, FFUPath, PerformFullFlashFirst, SkipWrite, Parts, DoResetFirst, ClearFlashingStatusAtEnd, CheckSectorAlignment, ShowProgress, Experimental); - } - - internal async static Task LumiaV2CustomFlash(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, List FlashParts, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null, string ProgrammerPath = null) - { - NokiaFlashModel Model = (NokiaFlashModel)Notifier.CurrentModel; - PhoneInfo Info = Model.ReadPhoneInfo(); - - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Model, 131072u); - - GPT GPT = new(GPTChunk); - - Partition UefiBSNV = GPT.GetPartition("UEFI_BS_NV"); - - bool UseOlderExploit = Info.UefiSecureBootEnabled; - - if (!UseOlderExploit && ClearFlashingStatusAtEnd) - { - if (FlashParts == null) - { - UseOlderExploit = true; - } - else - { - foreach (var part in FlashParts) - { - if (part.StartSector >= UefiBSNV.FirstSector && part.StartSector <= UefiBSNV.LastSector) - { - UseOlderExploit = true; - break; - } - } - } - } - - if (UseOlderExploit || !ClearFlashingStatusAtEnd) - { - await LumiaV2CustomFlashInternal(Notifier, FFUPath, PerformFullFlashFirst, SkipWrite, FlashParts, DoResetFirst, ClearFlashingStatusAtEnd, CheckSectorAlignment, ShowProgress, Experimental, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure, ProgrammerPath); - } - else - { - await LumiaV3FlashRomViewModel.LumiaV3CustomFlash(Notifier, FlashParts, CheckSectorAlignment, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure); - } - } - - // Magic! - internal async static Task LumiaV2CustomFlashInternal(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, List FlashParts, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null, string ProgrammerPath = null) //, string LoaderPath = null) - { - // Both SecurityHeader and StoreHeader need to be modified. - // Those should both not fall in a memory-gap to allow modification. - // The partial FFU header must be allocated in front of those headers, so the size of the partial header must be at least the size of the the SecurityHeader. - // Hashes take more space than descriptors, so the SecurityHeader will always be the biggest. - - bool AutoEmergencyReset = true; - bool Timeout; - - if (SetWorkingStatus == null) - { - SetWorkingStatus = (m, s, v, a, st) => { }; - } - - if (UpdateWorkingStatus == null) - { - UpdateWorkingStatus = (m, s, v, st) => { }; - } - - if (ExitSuccess == null) - { - ExitSuccess = (m, s) => { }; - } - - if (ExitFailure == null) - { - ExitFailure = (m, s) => { }; - } - - NokiaFlashModel Model = (NokiaFlashModel)Notifier.CurrentModel; - PhoneInfo Info = Model.ReadPhoneInfo(); - - string Type = Info.Type; - if (ProgrammerPath == null) - { - ProgrammerPath = GetProgrammerPath(Info.RKH, Type); - if (ProgrammerPath == null) - { - LogFile.Log("WARNING: No emergency programmer file found. Finding flash profile and rebooting phone may take a long time!", LogType.FileAndConsole); - } - } - List FFUs = null; - FlashProfile Profile; - if (FFUPath == null) - { - // Try to find an FFU from the repository for which there is also a known flashing profile - FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); - foreach (FFUEntry CurrentEntry in FFUs) - { - Profile = App.Config.GetProfile(Info.PlatformID, Info.Firmware, CurrentEntry.FirmwareVersion); - if (Profile != null) - { - FFUPath = CurrentEntry.Path; - break; - } - } - } - - if (FFUPath == null) - { - // Try to find any FFU with matching PlatformID in the repository - if (FFUs.Count > 0) - { - FFUPath = FFUs[0].Path; - } - } - - if (FFUPath == null) - { - throw new WPinternalsException("No valid profile FFU found in repository", "You can download necessary files in the "Download" section"); - } - - FFU FFU = new(FFUPath); - UInt32 UpdateType = ByteOperations.ReadUInt32(FFU.StoreHeader, 0); - if (UpdateType != 0) - { - throw new WPinternalsException("Only Full Flash images supported", "The provided FFU file reports that it doesn't support Full Flash updates, but may support something else such as Partial Flash updates. This is not supported."); - } - - if (FlashParts != null) - { - foreach (FlashPart Part in FlashParts) - { - if (Part.Stream == null) - { - throw new ArgumentException("Stream is null"); - } - - if (!Part.Stream.CanSeek) - { - throw new ArgumentException("Streams must be seekable"); - } - - if ((Part.StartSector * 0x200 % FFU.ChunkSize) != 0) - { - throw new ArgumentException("Invalid StartSector alignment"); - } - - if (CheckSectorAlignment && (Part.Stream.Length % FFU.ChunkSize) != 0) - { - throw new ArgumentException("Invalid Data length"); - } - } - } - - if ((Info.SecureFfuSupportedProtocolMask & ((ushort)FfuProtocol.ProtocolSyncV2)) == 0) // Exploit needs protocol v2 -> This check is not conclusive, because old phones also report support for this protocol, although it is really not supported. - { - throw new WPinternalsException("Flash failed!", "Protocols not supported. The phone reports that it does not support the Protocol Sync V2."); - } - - if (Info.FlashAppProtocolVersionMajor < 2) // Old phones do not support the hack. These phones have Flash protocol 1.x. - { - throw new WPinternalsException("Flash failed!", "Protocols not supported. The phone reports that Flash App communication protocol is lower than 2. Reported version by the phone: " + Info.FlashAppProtocolVersionMajor + "."); - } - - UEFI UEFI = new(FFU.GetPartition("UEFI")); - string BootMgrName = UEFI.EFIs.First(efi => efi.Name?.Contains("BootMgrApp") == true).Name; - UInt32 EstimatedSizeOfMemGap = (UInt32)UEFI.GetFile(BootMgrName).Length; - byte Options = 0; - if (SkipWrite) - { - Options = (byte)FlashOptions.SkipWrite; - } - - if (!Info.IsBootloaderSecure) - { - Options = (byte)((FlashOptions)Options | FlashOptions.SkipSignatureCheck); - } - - // Gap fill calculation: - // About 0x18000 of the gap is used for other purposes. - // Then round down to fill up the rest of the space, to make sure the memory for the headers is not allocated in a gap. - UInt32 EstimatedGapFill = FFU.RoundDownToChunks(EstimatedSizeOfMemGap - 0x18000); - - UInt32 MaximumGapFill; - int MaximumAttempts; - - if (!Experimental) - { - MaximumGapFill = FFU.RoundUpToChunks(2 * EstimatedSizeOfMemGap); - MaximumAttempts = (int)(((MaximumGapFill / FFU.ChunkSize) + 1) * 4); - } - else - { - MaximumGapFill = FFU.RoundUpToChunks(4 * EstimatedSizeOfMemGap); - MaximumAttempts = (int)(((MaximumGapFill / FFU.ChunkSize) + 1) * 8); - } - - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Model, (UInt32)FFU.ChunkSize); - - // Start with a reset - if (DoResetFirst) - { - SetWorkingStatus("Initializing flash...", "Rebooting phone", null, Status: WPinternalsStatus.Initializing); - - // When in flash mode, it is not possible to reboot straight to flash. - // Reboot and catch the phone in bootloader mode and then switch to flash context - Model.ResetPhone(); - - #region Properly recover from reset - many phones respond differently - - Timeout = false; - try - { - await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); - } - catch (TimeoutException) - { - Timeout = true; - } - if ((Notifier.CurrentInterface == null) && (!AutoEmergencyReset || Timeout)) - { - AutoEmergencyReset = false; - - if (Timeout) - { - LogFile.Log("The phone is not responding", LogType.ConsoleOnly); - LogFile.Log("It might be in emergency mode, while you have no matching driver installed", LogType.ConsoleOnly); - } - LogFile.Log("To continue the unlock-sequence, the phone needs to be rebooted", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", (Timeout ? "The phone is not responding. It might be in emergency mode, while you have no matching driver installed. " : "") + "To continue the unlock-sequence, the phone needs to be rebooted. Keep the phone connected to the PC. The unlock-sequence will resume automatically.", null, false, WPinternalsStatus.WaitingForManualReset); - - await Notifier.WaitForRemoval(); - - UpdateWorkingStatus("Initializing flash...", null, null); - - await Notifier.WaitForArrival(); - } - if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) - { - bool FailedToStartProgrammer = false; - if (ProgrammerPath != null) - { - QualcommSahara Sahara = new((QualcommSerial)Notifier.CurrentModel); - try - { - await Sahara.Reset(ProgrammerPath); - await Notifier.WaitForArrival(); - } - catch (BadConnectionException) - { - FailedToStartProgrammer = true; - } - catch (Exception ex) - { - LogFile.Log("An unexpected error happened", LogType.FileAndConsole); - LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); - FailedToStartProgrammer = true; - } - } - - if (ProgrammerPath == null || FailedToStartProgrammer) - { - ((QualcommSerial)Notifier.CurrentModel).Close(); // Prevent "Resource in use"; - - Timeout = false; - if (AutoEmergencyReset) - { - try - { - await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); - } - catch (TimeoutException) - { - Timeout = true; - } - catch (Exception ex) - { - LogFile.Log("An unexpected error happened", LogType.FileAndConsole); - LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); - FailedToStartProgrammer = true; - } - } - if (!AutoEmergencyReset || Timeout) - { - AutoEmergencyReset = false; - - if (!FailedToStartProgrammer) - { - LogFile.Log("The phone is in emergency mode and you didn't provide an emergency programmer", LogType.ConsoleOnly); - LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("To prevent this, provide an emergency programmer next time you will unlock a bootloader", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", - "The phone is in emergency mode and you didn't provide an emergency programmer." + - " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + - " Keep the phone connected to the PC." + - " The unlock-sequence will resume automatically. To prevent this, provide an emergency programmer next time you will unlock a bootloader.", - null, false, WPinternalsStatus.WaitingForManualReset); - } - else - { - LogFile.Log("The phone is in emergency mode and we couldn't start the emergency programmer", LogType.ConsoleOnly); - LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", - "The phone is in emergency mode and we couldn't start the emergency programmer." + - " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + - " Keep the phone connected to the PC." + - " The unlock-sequence will resume automatically.", - null, false, WPinternalsStatus.WaitingForManualReset); - } - await Notifier.WaitForRemoval(); - - UpdateWorkingStatus("Initializing flash...", null, null); - - await Notifier.WaitForArrival(); - } - } - } - - #endregion - - if ((Notifier.CurrentInterface != PhoneInterfaces.Lumia_Flash) && (Notifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader)) - { - throw new WPinternalsException("Phone is in wrong mode", "The phone should have been detected in bootloader or flash mode. Instead it has been detected in " + Notifier.CurrentInterface.ToString() + " mode."); - } - - Model = (NokiaFlashModel)Notifier.CurrentModel; - UpdateWorkingStatus("Initializing flash...", null, null); - } - - try - { - // This will succeed on new models - Model.SwitchToFlashAppContext(); - Model.DisableRebootTimeOut(); - } - catch - { - // This will succeed on old models - Model.ResetPhoneToFlashMode(); - await Notifier.WaitForArrival(); - Model = (NokiaFlashModel)Notifier.CurrentModel; - } - - // The payloads must be ordered by the number of locations - // - // FlashApp processes payloads like this: - // - First payloads which are with one location, those can be sent in bulk - // - Then payloads with more than one location, those should not be sent in bulk - // - // If you do not order payloads like this, you will get an error, most likely hash mismatch - // - FlashingPayload[] payloads = Array.Empty(); - if (FlashParts != null) - { - payloads = GetNonOptimizedPayloads(FlashParts, FFU.ChunkSize, (uint)(Info.WriteBufferSize / FFU.ChunkSize), SetWorkingStatus, UpdateWorkingStatus).OrderBy(x => x.TargetLocations.Length).ToArray(); - } - - bool AssumeImageHeaderFallsInGap = true; - bool AllocateAsyncBuffersOnPhone = true; - bool AllocateBackupBuffersOnPhone = false; - UInt32 CurrentGapFill = EstimatedGapFill; - UInt32 OldGapFill; - bool Success = false; - bool Abort = false; - bool PhoneNeedsReset = false; - bool WaitForReset = false; - int AttemptCount = 0; - UInt32 ExploitHeaderAllocationSize = 0; - UInt32 LastHeaderV2Size = 0; - byte[] PartialHeader; - byte[] FfuHeader; - UInt64 CombinedFFUHeaderSize; - Allocation SecurityHeaderAllocation = null; - Allocation ImageHeaderAllocation = null; - Allocation StoreHeaderAllocation = null; - Allocation PartialHeaderAllocation = null; - UInt32 HeaderOffset = 0; - bool Scanning = false; - bool ResetScanning = false; - - Profile = App.Config.GetProfile(Info.PlatformID, Info.Firmware, FFU.GetFirmwareVersion()); - if (Profile == null) - { - LogFile.Log("No flashing profile found", LogType.FileAndConsole); - } - else - { - if (ShowProgress) - { - LogFile.Log("Flashing profile loaded", LogType.FileAndConsole); - } - - CurrentGapFill = Profile.FillSize; - ExploitHeaderAllocationSize = Profile.HeaderSize; - AllocateAsyncBuffersOnPhone = Profile.AllocateAsyncBuffersOnPhone; - AssumeImageHeaderFallsInGap = Profile.AssumeImageHeaderFallsInGap; - } - - do - { - AttemptCount++; - if ((Profile == null) || (AttemptCount > 1)) - { - LogFile.Log("Custom flash attempt: " + AttemptCount + " of " + MaximumAttempts, LogType.FileAndConsole); - - if (!Scanning) - { - SetWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)MaximumAttempts, Status: WPinternalsStatus.Scanning); - } - - Scanning = true; - UpdateWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)AttemptCount, Status: WPinternalsStatus.Scanning); - - ExploitHeaderAllocationSize = CurrentGapFill + (UInt32)FFU.ChunkSize; - } - - // Initialize flash attempts - // Make sure async buffers are allocated on the phone before overflow attempts, - // or else failed attempts may cause more memory-gaps and allocation becomes more unpredictable. - // The phone is rebooted after each attempt (to avoid memory-corruption). - // And it seems that that normally all allocations are in a big memory-gap, which was created before BootMgr was loaded. - // And there is still memory allocated in a lower range. - // And by allocating USB buffers, it could cause more memory-scattering. - // On Lumia 950 2 USB buffers are allocated and on Lumia 930 there is only one USB buffer allocated. - // StartAsyncFlash() is needed on 950 and 640. But not needed on 930! - // In any case, the allocation of the async-buffers should not be simulated in the Uefi Memory Simulator, - // because that would create a gap in the Simulator, instead of avoiding a gap on the phone. - // - if (AllocateAsyncBuffersOnPhone) - { - Model.StartAsyncFlash(); - Model.EndAsyncFlash(); // Ending Async flashing is not necessary for Lumia 950, but it is necessary for Lumia 640! - } - - if (AllocateBackupBuffersOnPhone) - { - Model.BackupPartitionToRam("MODEM_FSG"); - Model.BackupPartitionToRam("MODEM_FS1"); - Model.BackupPartitionToRam("MODEM_FS2"); - Model.BackupPartitionToRam("SSD"); - Model.BackupPartitionToRam("DPP"); - } - - HeaderOffset = 0; - SecurityHeaderAllocation = null; - ImageHeaderAllocation = null; - StoreHeaderAllocation = null; - PartialHeaderAllocation = null; - UInt32 DestinationChunkIndex = 0; - - // Create memory map - UefiMemorySim.Reset(); - SecurityHeaderAllocation = UefiMemorySim.AllocatePool((uint)FFU.SecurityHeader.Length); - SecurityHeaderAllocation.CopyToThisAllocation(FFU.SecurityHeader, 0, (uint)FFU.SecurityHeader.Length, 0); - if (!AssumeImageHeaderFallsInGap) - { - ImageHeaderAllocation = UefiMemorySim.AllocatePool((uint)FFU.ImageHeader.Length); - ImageHeaderAllocation.CopyToThisAllocation(FFU.ImageHeader, 0, (uint)FFU.ImageHeader.Length, 0); - } - StoreHeaderAllocation = UefiMemorySim.AllocatePool((uint)FFU.StoreHeader.Length); - StoreHeaderAllocation.CopyToThisAllocation(FFU.StoreHeader, 0, (uint)FFU.StoreHeader.Length, 0); - - // Simulate sending partial header - PartialHeaderAllocation = UefiMemorySim.AllocatePool(ExploitHeaderAllocationSize); - - CombinedFFUHeaderSize = FFU.HeaderSize; - FfuHeader = new byte[CombinedFFUHeaderSize]; - UInt32 TotalPayloadCount = (uint)payloads.Length; - bool HeadersFull; - int FlashingPhase = 0; - UInt32 FlashingPhaseStartPayloadIndex = 0; - UInt32 FlashingPhasePayloadCount = 0; - bool FlashInProgress = false; - byte[] Buffer = new byte[FFU.ChunkSize]; - LastHeaderV2Size = 0; - do - { - HeadersFull = false; - - // On every flashing phase we must fill the memory gap first, before sending the header. - if (CurrentGapFill > UefiMemorySim.PageSize) - { - if (FlashingPhase > 0) - { - // Avoid Error 0x0010 "Invalid sub block length". - // Headersize must increase compared to last time a header was sent, to avoid processing the header. - // Offset must be 0, to reset buffers. - // Previous data + new data must fit in new headersize. - // We can send an extra byte, because last memory buffer was sent including the tail. - // And there is always extra space in the memoryspace after the tail. - Model.SendFfuHeaderV2(LastHeaderV2Size + 1, 0, new byte[1], Options); - } - - // CurrentGapFill is the amount of data we want to be allocated on the phone - // But we send less data, so the header won't be processed yet. - PartialHeader = new byte[UefiMemorySim.PageSize]; - Model.SendFfuHeaderV2(CurrentGapFill, 0, PartialHeader, Options); // Fill memory gap -> This will fail on phones with Flash Protocol v1.x !! On Lumia 640 this will hang on receiving the response when EndAsyncFlash was not called. - } - - using (FileStream FfuFile = new(FFU.Path, FileMode.Open, FileAccess.Read)) - { - // On every flashing phase we need to send the full header again to reset all the counters. - FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); - Model.SendFfuHeaderV1(FfuHeader, Options); - - if (PerformFullFlashFirst && (FlashingPhase == 0)) - { - // If we flash the stock ROM at this point, the header is in memory is not overwritten yet. - // This means that after the last chunk was written, the flashing-status is written to NV (and also flushed). - // But this doesn't matter, because even when we want to overwrite NV, this happens later. - // When the header is successfully overwritten in memory, the next chunk of custom data will be allowed to be written. - - LogFile.Log("Starting custom flash attempt by doing a full flash.", LogType.FileAndConsole); - - UInt64 Position = CombinedFFUHeaderSize; - byte[] FlashPayload; - int ChunkIndex = 0; - UInt32 TotalChunkCount = (UInt32)FFU.TotalChunkCount; - - // Protocol v2 - FlashPayload = new byte[Info.WriteBufferSize]; - - while (Position < (UInt64)FfuFile.Length) - { - UInt32 CommonFlashPayloadSize = Info.WriteBufferSize; - if (((UInt64)FfuFile.Length - Position) < CommonFlashPayloadSize) - { - CommonFlashPayloadSize = (UInt32)((UInt64)FfuFile.Length - Position); - FlashPayload = new byte[CommonFlashPayloadSize]; - } - - FfuFile.Read(FlashPayload, 0, (int)CommonFlashPayloadSize); - ChunkIndex += (int)(CommonFlashPayloadSize / FFU.ChunkSize); - Model.SendFfuPayloadV2(FlashPayload, ShowProgress ? (int)((double)(ChunkIndex + 1) * 100 / TotalChunkCount) : 0, 0); - Position += CommonFlashPayloadSize; - } - } - } - - UInt32 NewWriteDescriptorOffset = 0xF8; - UInt32 CatalogSize = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.ContentStart + 0x18); - UInt32 NewHashOffset = 0x20 + CatalogSize; - - UInt32 WriteDescriptorLength = 0; - UInt32 WriteDescriptorCount = 0; - UInt32 HashTableSize = 0; - - if (PerformFullFlashFirst && (FlashingPhase == 0)) - { - // Set offset for new descriptor to end of descriptor-table - WriteDescriptorLength = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD4); - NewWriteDescriptorOffset += WriteDescriptorLength; - - // Get descriptor-count of the FFU - WriteDescriptorCount = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD0); - - // Set offset for new hash to end of hash-table - HashTableSize = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.ContentStart + 0x1C); - NewHashOffset += HashTableSize; // Skip to the end of the original hash-table. - } - else - { - // From start of hash-table skip the first hashes for Image- and StoreHeaders. - HashTableSize = (UInt32)((FFU.ImageHeader.Length + FFU.StoreHeader.Length) / FFU.ChunkSize * 0x20); - NewHashOffset += HashTableSize; - } - - // Determine available space and number of payloads to send for this phase - UInt32 HashSpace = (UInt32)(FFU.SecurityHeader.Length - NewHashOffset); - UInt32 DescriptorSpace = (UInt32)(FFU.StoreHeader.Length - NewWriteDescriptorOffset); - - FlashingPhasePayloadCount = 0; - - // Always flash one extra chunk on the GPT (for purpose of testing and for making sure that first chunk does not contain all zero's). - UInt32 SecurityHeaderSize = FlashInProgress ? 0 : 0x20u; - UInt32 StoreHeaderSize = FlashInProgress ? 0 : 0x10u; - for (UInt32 i = FlashingPhaseStartPayloadIndex; i < payloads.Length; i++) - { - UInt32 NewSecurityHeaderSize = SecurityHeaderSize + payloads[i].GetSecurityHeaderSize(); - UInt32 NewStoreHeaderSize = StoreHeaderSize + payloads[i].GetStoreHeaderSize(); - - if (NewSecurityHeaderSize > HashSpace || NewStoreHeaderSize > DescriptorSpace) - { - HeadersFull = true; - break; - } - - FlashingPhasePayloadCount++; - SecurityHeaderSize = NewSecurityHeaderSize; - StoreHeaderSize = NewStoreHeaderSize; - } - - HashTableSize += SecurityHeaderSize; - WriteDescriptorCount += FlashingPhasePayloadCount + (FlashInProgress ? 0 : 1u); - WriteDescriptorLength += StoreHeaderSize; - - if (!ClearFlashingStatusAtEnd || HeadersFull) - { - WriteDescriptorCount++; - } - - // Write back new header values. - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD4, WriteDescriptorLength); - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD0, WriteDescriptorCount); - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.ContentStart + 0x1C, HashTableSize); - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xEC, 0); // FlashOnlyTableLength - Make flash progress bar white immediately. - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xE8, 1); // FlashOnlyTableCount - - // Write new descriptors - // First write descriptor and hash for the first GPT chunk - if (!FlashInProgress) // We only send the first GPT chunk when flash is not in progress yet. - { - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x00, 0x00000001); // Location count - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x04, 0x00000001); // Chunk count - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x08, 0x00000000); // Disk access method (0 = Begin, 2 = End) - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x0C, 0x00000000); // Chunk index = GPT - NewWriteDescriptorOffset += 0x10; - byte[] GPTHashValue = System.Security.Cryptography.SHA256.Create().ComputeHash(GPTChunk, 0, FFU.ChunkSize); // Hash is 0x20 bytes - System.Buffer.BlockCopy(GPTHashValue, 0, UefiMemorySim.Buffer, (int)(SecurityHeaderAllocation.ContentStart + NewHashOffset), 0x20); - NewHashOffset += 0x20; - } - - for (UInt32 i = 0; i < FlashingPhasePayloadCount; i++) - { - FlashingPayload payload = payloads[FlashingPhaseStartPayloadIndex + i]; - - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x00, (UInt32)payload.TargetLocations.Length); // Location count - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x04, payload.ChunkCount); // Chunk count - NewWriteDescriptorOffset += 0x08; - - foreach (UInt32 location in payload.TargetLocations) - { - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x00, 0x00000000); // Disk access method (0 = Begin, 2 = End) - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x04, location); // Chunk index - NewWriteDescriptorOffset += 0x08; - } - - foreach (byte[] hashValue in payload.ChunkHashes) - { - // Write new hash - System.Buffer.BlockCopy(hashValue, 0, UefiMemorySim.Buffer, (Int32)(SecurityHeaderAllocation.ContentStart + NewHashOffset), 0x20); - NewHashOffset += 0x20; - } - } - - Stream CurrentStream = null; - int StreamIndex = 0; - int Step = 0; - try - { - // Send a small portion of header v2 at offset 0 - // The payload is smaller than the total headersize, so that it won't start processing the header now. - // This will allocate new memory at the bottom of the memory-pool, but it will not reset the previously imported ffu header. - Step = 1; - PartialHeader = new byte[UefiMemorySim.PageSize]; - Model.SendFfuHeaderV2(ExploitHeaderAllocationSize, 0, PartialHeader, Options); // SkipWrite = 1 (only works on engineering phones) - - // Now we will send the rest of the exploit header, but we will increase the total size even higher, so that it still won't start processing the headers. - // We've send only a small first part of the header. The allocated header was bigger: ExploitHeaderAllocationSize. - // But we HAVE to send the whole header. We can't skip a part. - Step = 2; - UInt32 ExploitHeaderRemaining = SecurityHeaderAllocation.TailEnd + 1 - PartialHeaderAllocation.ContentStart - (UInt32)PartialHeader.Length; - HeaderOffset = (UInt32)PartialHeader.Length; - while (ExploitHeaderRemaining > 0) - { - UInt32 CurrentFill = ExploitHeaderRemaining; - if (CurrentFill > Info.WriteBufferSize) - { - CurrentFill = Info.WriteBufferSize; - } - - PartialHeader = new byte[CurrentFill]; - PartialHeaderAllocation.CopyFromThisAllocation(HeaderOffset, CurrentFill, PartialHeader, 0); - Model.SendFfuHeaderV2(HeaderOffset + CurrentFill + 1, HeaderOffset, PartialHeader, Options); // Phone may crash here. USB write is done. USB read might fail due to crash. Happens on my own Lumia 650. - LastHeaderV2Size = HeaderOffset + CurrentFill + 1; - ExploitHeaderRemaining -= CurrentFill; - HeaderOffset += CurrentFill; - } - - // Send custom payload - Step = 3; - Int32 payloadCount = 0; - byte[] payloadBuffer = new byte[Info.WriteBufferSize]; - bool sendPayload = false; - for (Int32 i = FlashInProgress ? 0 : -1; i < FlashingPhasePayloadCount; i++) - { - string NewProgressText = "Flashing resources..."; - if (!FlashInProgress) - { - // First send the GPT chunk - Step = 4; - System.Buffer.BlockCopy(GPTChunk, 0, Buffer, 0, FFU.ChunkSize); - - Step = 8; - // This may fail. Normally with WPinternalsException for Invalid Hash or Data not aligned. - // Or it may fail with a BadConnectionException when the phone crashes and drops the connection. - Model.SendFfuPayloadV1(Buffer, 0); - if (!FlashInProgress) - { - Step = 9; - if (ShowProgress) - { - LogFile.Log("Flashing in progress!", LogType.FileAndConsole); - } - - FlashInProgress = true; - Scanning = false; - SetWorkingStatus(null, null, (UInt64?)payloads.Length, Status: WPinternalsStatus.Flashing); - } - } - else - { - Step = 5; - - FlashingPayload payload = payloads[FlashingPhaseStartPayloadIndex + i]; - - if (payloadCount == ((Info.WriteBufferSize / FFU.ChunkSize) - 1)) - { - sendPayload = true; - } - - if (FlashingPhaseStartPayloadIndex + i + 1 >= FlashingPhasePayloadCount) - { - sendPayload = true; - byte[] tmpBuffer = new byte[(payloadCount + 1) * FFU.ChunkSize]; - System.Buffer.BlockCopy(payloadBuffer, 0, tmpBuffer, 0, payloadCount * FFU.ChunkSize); - payloadBuffer = tmpBuffer; - } - - // Check if the next payload contains more than one chunk - if (!sendPayload && FlashingPhaseStartPayloadIndex + i + 1 < FlashingPhasePayloadCount && (payloads[FlashingPhaseStartPayloadIndex + i + 1].ChunkCount != 1 || payloads[FlashingPhaseStartPayloadIndex + i + 1].TargetLocations.Length != 1)) - { - sendPayload = true; - byte[] tmpBuffer = new byte[(payloadCount + 1) * FFU.ChunkSize]; - System.Buffer.BlockCopy(payloadBuffer, 0, tmpBuffer, 0, payloadCount * FFU.ChunkSize); - payloadBuffer = tmpBuffer; - } - - // We prepare the buffer setup above with all consecutive chunks we have to send in - // We can't send a single chunk otherwise we would get 0x1007: Payload data does not contain all data - if (payload.ChunkCount != 1) - { - NewProgressText = "Flashing common resources..."; - payloadBuffer = new byte[payload.ChunkCount * FFU.ChunkSize]; - for (uint j = 0; j < payload.ChunkCount; j++) - { - StreamIndex = (Int32)payload.StreamIndexes[j]; - FlashPart flashPart = FlashParts[StreamIndex]; - CurrentStream = flashPart.Stream; - CurrentStream.Seek(payload.StreamLocations[j], SeekOrigin.Begin); - - Step = 6; - Array.Clear(payloadBuffer, (Int32)(FFU.ChunkSize * j), FFU.ChunkSize); // Not really needed anymore? - - Step = 7; - CurrentStream.Read(payloadBuffer, (Int32)(FFU.ChunkSize * j), FFU.ChunkSize); - } - } - - if (payload.TargetLocations.Length != 1) - { - NewProgressText = "Flashing common resources..."; - payloadBuffer = new byte[FFU.ChunkSize]; - } - - if (payload.ChunkCount == 1) - { - StreamIndex = (Int32)payload.StreamIndexes[0]; - FlashPart flashPart = FlashParts[StreamIndex]; - CurrentStream = flashPart.Stream; - CurrentStream.Seek(payload.StreamLocations[0], SeekOrigin.Begin); - - if (payload.TargetLocations.Length == 1 && !string.IsNullOrEmpty(flashPart.ProgressText)) - { - NewProgressText = flashPart.ProgressText; - } - - CurrentStream.Read(payloadBuffer, FFU.ChunkSize * payloadCount, FFU.ChunkSize); - } - - Step = 8; - // This may fail. Normally with WPinternalsException for Invalid Hash or Data not aligned. - // Or it may fail with a BadConnectionException when the phone crashes and drops the connection. - - payloadCount++; - } - - UpdateWorkingStatus(NewProgressText, null, (UInt64?)(FlashingPhaseStartPayloadIndex + i + 1), WPinternalsStatus.Flashing); - - if (i != -1 && sendPayload) - { - // This fails when sending multiple chunks per payload with 0x1003: Hash mismatch - Model.SendFfuPayloadV2(payloadBuffer, ShowProgress ? (Int32)((FlashingPhaseStartPayloadIndex + i + 1) * 100 / payloads.Length) : 0); - sendPayload = false; - payloadCount = 0; - payloadBuffer = new byte[Info.WriteBufferSize]; - } - - DestinationChunkIndex++; - } - - Step = 10; - FlashingPhaseStartPayloadIndex += FlashingPhasePayloadCount; - - Step = 11; - if (!HeadersFull) - { - Step = 12; - App.Config.SetProfile(Info.Type, Info.PlatformID, Info.ProductCode, Info.Firmware, FFU.GetFirmwareVersion(), CurrentGapFill, ExploitHeaderAllocationSize, AssumeImageHeaderFallsInGap, AllocateAsyncBuffersOnPhone); - if (ShowProgress) - { - LogFile.Log("Custom flash succeeded!", LogType.FileAndConsole); - } - - Success = true; - } - else - { - // At this point we're missing a few payloads, so we need to start again. - LogFile.Log("Reinitilizing a new flashing attempt because headers were full and we're not quite done yet!"); - } - } - catch (BadConnectionException) - { - LogFile.Log("Connection to phone is lost - " + - Step.ToString() + " " + - StreamIndex.ToString() + " " + - (CurrentStream == null ? "0" : CurrentStream.Position.ToString()) + " " + - FlashingPhase.ToString() + " " + - FlashingPhaseStartPayloadIndex.ToString() + " " + - DestinationChunkIndex.ToString()); - LogFile.Log("Expect phone to reboot", LogType.FileAndConsole); - WaitForReset = true; - } - catch (Exception Ex) - { - if (FlashInProgress) - { - // Normally, when we end up here, we were not in process of flashing yet. - // It would be a flash attempt which failed. - // But if we were already flashing, then something else is wrong. - // We need more info and stop flashing. - LogFile.Log("Custom flash failed", LogType.FileAndConsole); - LogFile.LogException(Ex, LogType.FileOnly, - Step.ToString() + " " + - StreamIndex.ToString() + " " + - (CurrentStream == null ? "0" : CurrentStream.Position.ToString()) + " " + - FlashingPhase.ToString() + " " + - FlashingPhaseStartPayloadIndex.ToString() + " " + - DestinationChunkIndex.ToString()); - Abort = true; - } - else - { - LogFile.Log("Custom flash attempt failed", LogType.FileAndConsole); - LogFile.LogException(Ex, LogType.FileOnly, - Step.ToString() + " " + - StreamIndex.ToString() + " " + - (CurrentStream == null ? "0" : CurrentStream.Position.ToString()) + " " + - FlashingPhase.ToString() + " " + - FlashingPhaseStartPayloadIndex.ToString() + " " + - DestinationChunkIndex.ToString()); - } - - PhoneNeedsReset = true; - } - - if (FlashInProgress) - { - FlashingPhase++; - } - } - while (HeadersFull && FlashInProgress && !Abort); - - if (!Success) - { - if ((Profile != null) && !Abort) - { - LogFile.Log("Flashing profile was loaded, but it is not working", LogType.FileAndConsole); - LogFile.Log("Attempting to find a working profile", LogType.FileAndConsole); - ResetScanning = true; - } - - if (PhoneNeedsReset) - { - Model.ResetPhone(); - WaitForReset = true; - } - - if (WaitForReset) - { - #region Properly recover from reset between flash attempts - many phones respond differently - - Timeout = false; - try - { - await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); - } - catch (TimeoutException) - { - Timeout = true; - } - if ((Notifier.CurrentInterface == null) && (!AutoEmergencyReset || Timeout)) - { - AutoEmergencyReset = false; - - if (Timeout) - { - LogFile.Log("The phone is not responding", LogType.ConsoleOnly); - LogFile.Log("It might be in emergency mode, while you have no matching driver installed", LogType.ConsoleOnly); - } - LogFile.Log("To continue the unlock-sequence, the phone needs to be rebooted", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - UpdateWorkingStatus("You need to manually reset your phone now!", (Timeout ? "The phone is not responding. It might be in emergency mode, while you have no matching driver installed. " : "") + "To continue the unlock-sequence, the phone needs to be rebooted. Keep the phone connected to the PC. The unlock-sequence will resume automatically.", null, WPinternalsStatus.WaitingForManualReset); - - await Notifier.WaitForRemoval(); - - UpdateWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)AttemptCount, Status: WPinternalsStatus.Scanning); - - await Notifier.WaitForArrival(); - } - if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) - { - bool FailedToStartProgrammer = false; - if (ProgrammerPath != null) - { - QualcommSahara Sahara = new((QualcommSerial)Notifier.CurrentModel); - try - { - await Sahara.Reset(ProgrammerPath); - await Notifier.WaitForArrival(); - } - catch (BadConnectionException) - { - FailedToStartProgrammer = true; - } - } - - if (ProgrammerPath == null || FailedToStartProgrammer) - { - ((QualcommSerial)Notifier.CurrentModel).Close(); // Prevent "Resource in use"; - - Timeout = false; - if (AutoEmergencyReset) - { - try - { - await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); - } - catch (TimeoutException) - { - Timeout = true; - } - catch (Exception ex) - { - LogFile.Log("An unexpected error happened", LogType.FileAndConsole); - LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); - FailedToStartProgrammer = true; - } - } - if (!AutoEmergencyReset || Timeout) - { - AutoEmergencyReset = false; - if (!FailedToStartProgrammer) - { - LogFile.Log("The phone is in emergency mode and you didn't provide an emergency programmer", LogType.ConsoleOnly); - LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("To prevent this, provide an emergency programmer next time you will unlock a bootloader", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", - "The phone is in emergency mode and you didn't provide an emergency programmer." + - " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + - " Keep the phone connected to the PC." + - " The unlock-sequence will resume automatically. To prevent this, provide an emergency programmer next time you will unlock a bootloader.", - null, false, WPinternalsStatus.WaitingForManualReset); - } - else - { - LogFile.Log("The phone is in emergency mode and we couldn't start the emergency programmer", LogType.ConsoleOnly); - LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", - "The phone is in emergency mode and we couldn't start the emergency programmer." + - " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + - " Keep the phone connected to the PC." + - " The unlock-sequence will resume automatically.", - null, false, WPinternalsStatus.WaitingForManualReset); - } - - await Notifier.WaitForRemoval(); - - UpdateWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)AttemptCount, Status: WPinternalsStatus.Scanning); - - await Notifier.WaitForArrival(); - } - } - } - - #endregion - - // Sanity check: must be in flash mode - if ((Notifier.CurrentInterface != PhoneInterfaces.Lumia_Flash) && (Notifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader)) - { - break; - } - - Model = (NokiaFlashModel)Notifier.CurrentModel; - - // In case we are on an Engineering phone which isn't stuck in flashmode and booted to BootMgrApp - Model.SwitchToFlashAppContext(); - Model.DisableRebootTimeOut(); - } - - PhoneNeedsReset = false; - WaitForReset = false; - - // Calculate variables for next attempt - if (ResetScanning) - { - AssumeImageHeaderFallsInGap = true; - AllocateAsyncBuffersOnPhone = true; - AllocateBackupBuffersOnPhone = false; - CurrentGapFill = EstimatedGapFill; - Profile = null; - ResetScanning = false; - } - else if (Experimental && !AllocateBackupBuffersOnPhone) - { - AllocateBackupBuffersOnPhone = true; - } - else - { - AllocateBackupBuffersOnPhone = false; - if (AllocateAsyncBuffersOnPhone) - { - AllocateAsyncBuffersOnPhone = false; - } - else - { - AllocateAsyncBuffersOnPhone = true; - OldGapFill = CurrentGapFill; - if (OldGapFill <= EstimatedGapFill) - { - CurrentGapFill = EstimatedGapFill + (EstimatedGapFill - OldGapFill) + (UInt32)FFU.ChunkSize; - if (CurrentGapFill > MaximumGapFill) - { - if (OldGapFill > 0) - { - CurrentGapFill = OldGapFill - (UInt32)FFU.ChunkSize; - } - else if (AssumeImageHeaderFallsInGap) - { - AssumeImageHeaderFallsInGap = false; - CurrentGapFill = EstimatedGapFill; - } - else - { - break; - } - } - } - else - { - if (OldGapFill <= (EstimatedGapFill * 2)) - { - CurrentGapFill = EstimatedGapFill - (OldGapFill - EstimatedGapFill); - } - else - { - CurrentGapFill = OldGapFill + (UInt32)FFU.ChunkSize; - if (CurrentGapFill > MaximumGapFill) - { - if (AssumeImageHeaderFallsInGap) - { - AssumeImageHeaderFallsInGap = false; - CurrentGapFill = EstimatedGapFill; - } - else - { - break; - } - } - } - } - } - } - } - } - while (!Success && !Abort); - - // Now we will first try to create a memory corruption in the phone, before we reset the phone. - // The memory corruption will cause the phone to abort the shutdown-sequence and switch to emergency mode immediately. - // We already avoided that the FlashingStatus was written to NV. - // But we also need to avoid that the BootFlag is written to NV when the phone is properly shut down, because that will overwrite the NV vars we wrote earlier. - if (Success && !ClearFlashingStatusAtEnd) - { - // Make the phone crash here! - // This will actually make the phone crash when it frees memory during shutdown or reboot of the phone - - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.HeadStart + 4, 0); // Set allocation size to 0 in allocationhead - ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.HeadStart + 4, 0); // Set allocation size to 0 in allocationhead - if (CurrentGapFill > UefiMemorySim.PageSize) - { - Model.SendFfuHeaderV2(LastHeaderV2Size + 1, 0, new byte[1], Options); - PartialHeader = new byte[UefiMemorySim.PageSize]; - Model.SendFfuHeaderV2(CurrentGapFill, 0, PartialHeader, Options); // Fill memory gap - } - using (FileStream FfuFile = new(FFU.Path, FileMode.Open, FileAccess.Read)) - { - // On every flashing phase we need to send the full header again, because this triggers ffu_import_invalidate(), which is necessary to reset all the counters. - FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); - Model.SendFfuHeaderV1(FfuHeader, Options); - } - PartialHeader = new byte[UefiMemorySim.PageSize]; - Model.SendFfuHeaderV2(ExploitHeaderAllocationSize, 0, PartialHeader, Options); // SkipWrite = 1 (only works on engineering phones) - UInt32 ExploitHeaderRemaining = SecurityHeaderAllocation.TailEnd + 1 - PartialHeaderAllocation.ContentStart - (UInt32)PartialHeader.Length; - HeaderOffset = (UInt32)PartialHeader.Length; - while (ExploitHeaderRemaining > 0) - { - UInt32 CurrentFill = ExploitHeaderRemaining; - if (CurrentFill > Info.WriteBufferSize) - { - CurrentFill = Info.WriteBufferSize; - } - - PartialHeader = new byte[CurrentFill]; - PartialHeaderAllocation.CopyFromThisAllocation(HeaderOffset, CurrentFill, PartialHeader, 0); - Model.SendFfuHeaderV2(HeaderOffset + CurrentFill + 1, HeaderOffset, PartialHeader, Options); - LastHeaderV2Size = HeaderOffset + CurrentFill + 1; - ExploitHeaderRemaining -= CurrentFill; - HeaderOffset += CurrentFill; - } - - // Do the actual reset, which will result in a crash while cleaning up memory - ((NokiaFlashModel)Notifier.CurrentModel).ResetPhone(); - - LogFile.Log("Phone performs hard exit", LogType.FileAndConsole); - - #region Properly recover from reset at the end of custom flash - many phones respond differently - - // Wait for the phone to boot to emergency mode - // Emergency mode is always triggered on purpose to avoid writing NV vars - // We also wait for emergency mode when no valid programmer is present, or else caller-code will not know at which stage the phone is rebooting - - // Possibilities here: - // - Lumia 950 or 950 XL which does not crash to emergency mode, switching to mass storage mode, but not assigning a drive-letter -> Bootmgr, Nothing - // - Lumia 950 or 950 XL which does not crash to emergency mode, switching to mass storage mode, and assigning a drive-letter -> Bootmgr, MSM - // - Other Lumia's, switching to mass storage mode, but not assigning a drive-letter -> Bootmgr, Nothing - - Timeout = false; - try - { - await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); - } - catch (TimeoutException) - { - Timeout = true; - } - if ((Notifier.CurrentInterface == null) && (!AutoEmergencyReset || Timeout)) - { - AutoEmergencyReset = false; - - if (Timeout) - { - LogFile.Log("The phone is not responding", LogType.ConsoleOnly); - LogFile.Log("It might be in emergency mode, while you have no matching driver installed", LogType.ConsoleOnly); - } - LogFile.Log("To continue the unlock-sequence, the phone needs to be rebooted", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", (Timeout ? "The phone is not responding. It might be in emergency mode, while you have no matching driver installed. " : "") + "To continue the unlock-sequence, the phone needs to be rebooted. Keep the phone connected to the PC. The unlock-sequence will resume automatically.", null, false, WPinternalsStatus.WaitingForManualReset); - - await Notifier.WaitForRemoval(); - - SetWorkingStatus("Rebooting phone..."); - } - if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) - { - bool FailedToStartProgrammer = false; - if (ProgrammerPath != null) - { - QualcommSahara Sahara = new((QualcommSerial)Notifier.CurrentModel); - try - { - await Sahara.Reset(ProgrammerPath); - await Notifier.WaitForArrival(); - } - catch (BadConnectionException) - { - FailedToStartProgrammer = true; - } - } - - if (ProgrammerPath == null || FailedToStartProgrammer) - { - ((QualcommSerial)Notifier.CurrentModel).Close(); // Prevent "Resource in use"; - - Timeout = false; - if (AutoEmergencyReset) - { - try - { - await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); - } - catch (TimeoutException) - { - Timeout = true; - } - catch (Exception ex) - { - LogFile.Log("An unexpected error happened", LogType.FileAndConsole); - LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); - FailedToStartProgrammer = true; - } - } - if (!AutoEmergencyReset || Timeout) - { - AutoEmergencyReset = false; - - if (!FailedToStartProgrammer) - { - LogFile.Log("The phone is in emergency mode and you didn't provide an emergency programmer", LogType.ConsoleOnly); - LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("To prevent this, provide an emergency programmer next time you will unlock a bootloader", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", - "The phone is in emergency mode and you didn't provide an emergency programmer." + - " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + - " Keep the phone connected to the PC." + - " The unlock-sequence will resume automatically. To prevent this, provide an emergency programmer next time you will unlock a bootloader.", - null, false, WPinternalsStatus.WaitingForManualReset); - } - else - { - LogFile.Log("The phone is in emergency mode and we couldn't start the emergency programmer", LogType.ConsoleOnly); - LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); - LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); - LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); - LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); - LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); - - SetWorkingStatus("You need to manually reset your phone now!", - "The phone is in emergency mode and we couldn't start the emergency programmer." + - " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + - " Keep the phone connected to the PC." + - " The unlock-sequence will resume automatically.", - null, false, WPinternalsStatus.WaitingForManualReset); - } - - await Notifier.WaitForRemoval(); - - SetWorkingStatus("Rebooting phone..."); - - // await Notifier.WaitForArrival(); // Function will exit while phone is rebooting - } - } - } - - #endregion - } - else - { - // If we didn't do a hard exit, we need to do a normal reboot - ((NokiaFlashModel)Notifier.CurrentModel).ResetPhone(); - } - - if (Success) - { - ExitSuccess("Flash succeeded!", null); - } - else - { - throw new WPinternalsException("Custom flash failed"); - } - } - - internal class FlashingPayload - { - public UInt32 ChunkCount; - public byte[][] ChunkHashes; - public UInt32[] TargetLocations; - public UInt32[] StreamIndexes; - public Int64[] StreamLocations; - - public FlashingPayload(UInt32 ChunkCount, byte[][] ChunkHashes, UInt32[] TargetLocations, UInt32[] StreamIndexes, Int64[] StreamLocations) - { - this.ChunkCount = ChunkCount; - this.ChunkHashes = ChunkHashes; - this.TargetLocations = TargetLocations; - this.StreamIndexes = StreamIndexes; - this.StreamLocations = StreamLocations; - } - - public UInt32 GetSecurityHeaderSize() - { - return 0x20 * (UInt32)ChunkHashes.Length; - } - - public UInt32 GetStoreHeaderSize() - { - return 0x08 * ((UInt32)TargetLocations.Length + 1); - } - } - - // - // Function to fall back into the legacy implementation of custom flash, to test the modifications done in the custom flash function - // in LumiaV2UnlockBootViewModel - // - internal static FlashingPayload[] GetNonOptimizedPayloads(List flashParts, Int32 chunkSize, UInt32 MaximumChunkCount, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null) - { - long TotalProcess1 = 0; - for (Int32 j = 0; j < flashParts.Count; j++) - { - FlashPart flashPart = flashParts[j]; - TotalProcess1 += flashPart.Stream.Length / chunkSize; - } - - ulong CurrentProcess1 = 0; - SetWorkingStatus("Hashing resources...", "Initializing flash...", (UInt64)TotalProcess1, Status: WPinternalsStatus.Initializing); - - var crypto = System.Security.Cryptography.SHA256.Create(); - List flashingPayloads = new(); - if (flashParts == null) - { - return flashingPayloads.ToArray(); - } - - for (UInt32 j = 0; j < flashParts.Count; j++) - { - FlashPart flashPart = flashParts[(Int32)j]; - flashPart.Stream.Seek(0, SeekOrigin.Begin); - var totalChunkCount = flashPart.Stream.Length / chunkSize; - for (UInt32 i = 0; i < totalChunkCount; i++) - { - UpdateWorkingStatus("Hashing resources...", "Initializing flash...", CurrentProcess1, WPinternalsStatus.Initializing); - byte[] buffer = new byte[chunkSize]; - Int64 position = flashPart.Stream.Position; - flashPart.Stream.Read(buffer, 0, chunkSize); - flashingPayloads.Add(new FlashingPayload(1, new byte[][] { crypto.ComputeHash(buffer) }, new UInt32[] { (flashPart.StartSector * 0x200 / (UInt32)chunkSize) + i }, new UInt32[] { j }, new Int64[] { position })); - CurrentProcess1++; - } - } - - return flashingPayloads.ToArray(); - } - - // - // This function finds in an optimized way the number of duplicate chunks in a given stream, and returns - // a list of elements, defining a chunk occurence in said stream and the chunk precomputed SHA256 hash. - // - internal static FlashingPayload[] GetOptimizedPayloads(List flashParts, Int32 chunkSize, UInt32 MaximumChunkCount, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null) - { - List flashingPayloads = new(); - if (flashParts == null) - { - return flashingPayloads.ToArray(); - } - - long TotalProcess1 = 0; - for (Int32 j = 0; j < flashParts.Count; j++) - { - FlashPart flashPart = flashParts[j]; - TotalProcess1 += flashPart.Stream.Length / chunkSize; - } - - ulong CurrentProcess1 = 0; - SetWorkingStatus("Hashing resources...", "Initializing flash...", (UInt64)TotalProcess1, Status: WPinternalsStatus.Initializing); - - using (System.Security.Cryptography.SHA256 crypto = System.Security.Cryptography.SHA256.Create()) - { - for (UInt32 j = 0; j < flashParts.Count; j++) - { - FlashPart flashPart = flashParts[(Int32)j]; - flashPart.Stream.Seek(0, SeekOrigin.Begin); - var totalChunkCount = flashPart.Stream.Length / chunkSize; - for (UInt32 i = 0; i < totalChunkCount; i++) - { - UpdateWorkingStatus("Hashing resources...", "Initializing flash...", CurrentProcess1, WPinternalsStatus.Initializing); - byte[] buffer = new byte[chunkSize]; - Int64 position = flashPart.Stream.Position; - flashPart.Stream.Read(buffer, 0, chunkSize); - var hash = crypto.ComputeHash(buffer); - - if (flashingPayloads.Any(x => ByteOperations.Compare(x.ChunkHashes[0], hash))) - { - var payloadIndex = flashingPayloads.FindIndex(x => ByteOperations.Compare(x.ChunkHashes[0], hash)); - var locationList = flashingPayloads[payloadIndex].TargetLocations.ToList(); - locationList.Add((flashPart.StartSector * 0x200 / (UInt32)chunkSize) + i); - flashingPayloads[payloadIndex].TargetLocations = locationList.ToArray(); - } - else - { - flashingPayloads.Add(new FlashingPayload(1, new byte[][] { hash }, new UInt32[] { (flashPart.StartSector * 0x200 / (UInt32)chunkSize) + i }, new UInt32[] { j }, new Int64[] { position })); - } - - CurrentProcess1++; - } - } - } - - return flashingPayloads.ToArray(); - } - - internal static string GetProgrammerPath(byte[] RKH, string Type) - { - IEnumerable RKHEntries = App.Config.EmergencyRepository.Where(e => StructuralComparisons.StructuralEqualityComparer.Equals(e.RKH, RKH) && e.ProgrammerExists()); - if (RKHEntries.Any()) - { - if (RKHEntries.Count() == 1) - { - return RKHEntries.First().ProgrammerPath; - } - else - { - EmergencyFileEntry RKHEntry = RKHEntries.FirstOrDefault(e => string.Equals(e.Type, Type, StringComparison.CurrentCulture)); - if (RKHEntry != null) - { - return RKHEntry.ProgrammerPath; - } - else - { - return RKHEntries.First().ProgrammerPath; // Cannot be sure this is the right one!! - } - } - } - else - { - EmergencyFileEntry TypeEntry = App.Config.EmergencyRepository.Find(e => string.Equals(e.Type, Type, StringComparison.CurrentCulture) && e.ProgrammerExists()); - if (TypeEntry != null) - { - return TypeEntry.ProgrammerPath; - } - else - { - return null; - } - } - } - - // Assumes phone with Flash protocol v2 - // Assumes phone is in flash mode - internal async static Task LumiaV2FlashArchive(PhoneNotifierViewModel Notifier, string ArchivePath, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) - { - LogFile.BeginAction("FlashCustomROM"); - - NokiaFlashModel FlashModel = (NokiaFlashModel)Notifier.CurrentModel; - - // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. - // We need the fist sector if we want to write back the GPT. - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); - GPT GPT = new(GPTChunk); - - Partition Target; - FlashPart Part; - List Parts = new(); - ulong MainOSOldSectorCount = 0; - ulong MainOSNewSectorCount = 0; - ulong DataOldSectorCount = 0; - ulong DataNewSectorCount = 0; - ulong FirstMainOSSector = 0; - int PartitionCount = 0; - ulong TotalSizeSectors = 0; - bool IsUnlocked = false; - bool GPTChanged = false; - - bool ClearFlashingStatus = true; - - if (SetWorkingStatus == null) - { - SetWorkingStatus = (m, s, v, a, st) => { }; - } - - if (UpdateWorkingStatus == null) - { - UpdateWorkingStatus = (m, s, v, st) => { }; - } - - if (ExitSuccess == null) - { - ExitSuccess = (m, s) => { }; - } - - if (ExitSuccess == null) - { - ExitSuccess = (m, s) => { }; - } - - SetWorkingStatus("Initializing flash...", null, null, Status: WPinternalsStatus.Initializing); - - try - { - using FileStream FileStream = new(ArchivePath, FileMode.Open); - using ZipArchive Archive = new(FileStream, ZipArchiveMode.Read); - // Determine if there is a partition layout present - ZipArchiveEntry PartitionEntry = Archive.GetEntry("Partitions.xml"); - if (PartitionEntry == null) - { - GPT.MergePartitions(null, true, Archive); - GPTChanged |= GPT.HasChanged; - } - else - { - using Stream ZipStream = PartitionEntry.Open(); - using StreamReader ZipReader = new(ZipStream); - string PartitionXml = ZipReader.ReadToEnd(); - GPT.MergePartitions(PartitionXml, true, Archive); - GPTChanged |= GPT.HasChanged; - } - - // First determine if we need a new GPT! - foreach (ZipArchiveEntry Entry in Archive.Entries) - { - if (!Entry.FullName.Contains("/")) // No subfolders - { - string PartitionName = Entry.Name; - int Pos = PartitionName.IndexOf('.'); - if (Pos >= 0) - { - PartitionName = PartitionName.Substring(0, Pos); - } - - Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); - if (Partition != null) - { - using DecompressedStream DecompressedStream = new(Entry.Open()); - ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; - try - { - StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; - } - catch { } - - TotalSizeSectors += StreamLengthInSectors; - PartitionCount++; - - if (string.Equals(PartitionName, "MainOS", StringComparison.CurrentCultureIgnoreCase)) - { - MainOSOldSectorCount = Partition.SizeInSectors; - MainOSNewSectorCount = StreamLengthInSectors; - FirstMainOSSector = Partition.FirstSector; - } - else if (string.Equals(PartitionName, "Data", StringComparison.CurrentCultureIgnoreCase)) - { - DataOldSectorCount = Partition.SizeInSectors; - DataNewSectorCount = StreamLengthInSectors; - } - else if (StreamLengthInSectors > Partition.SizeInSectors) - { - LogFile.Log("Flash failed! Size of partition '" + PartitionName + "' is too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Size of partition '" + PartitionName + "' is too big."); - return; - } - else if (string.Equals(PartitionName, "EFIESP", StringComparison.CurrentCultureIgnoreCase)) - { - ulong EfiespLength = StreamLengthInSectors * 0x200; - byte[] EfiespBinary = new byte[EfiespLength]; - DecompressedStream.Read(EfiespBinary, 0, (int)EfiespLength); - IsUnlocked = (ByteOperations.ReadUInt32(EfiespBinary, 0x20) == (EfiespBinary.Length / 0x200 / 2)) && ByteOperations.ReadAsciiString(EfiespBinary, (UInt32)(EfiespBinary.Length / 2) + 3, 8) == "MSDOS5.0"; - if (IsUnlocked) - { - Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); - if (IsUnlockedFlag == null) - { - IsUnlockedFlag = new Partition - { - Name = "IS_UNLOCKED", - Attributes = 0, - PartitionGuid = Guid.NewGuid(), - PartitionTypeGuid = Guid.NewGuid(), - FirstSector = 0x40, - LastSector = 0x40 - }; - GPT.Partitions.Add(IsUnlockedFlag); - GPTChanged = true; - ClearFlashingStatus = false; - } - } - else - { - Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); - if (IsUnlockedFlag != null) - { - GPT.Partitions.Remove(IsUnlockedFlag); - GPTChanged = true; - ClearFlashingStatus = false; - } - } - } - } - } - } - - if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) - { - if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) - { - UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; - if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) - { - // MainOS and Data partitions need to be re-aligned! - Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); - Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); - MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; - DataPartition.FirstSector = MainOSPartition.LastSector + 1; - if ((DataPartition.FirstSector % 0x100) > 0) - { - DataPartition.FirstSector = (DataPartition.FirstSector + 0x100) / 0x100 * 0x100; - } - - DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; - - GPTChanged = true; - } - else - { - LogFile.Log("Flash failed! Sizes of partitions 'MainOS' and 'Data' together are too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); - return; - } - } - } - else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'MainOS' is too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); - return; - } - else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'Data' is too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Size of partition 'Data' is too big."); - return; - } - - if (!ClearFlashingStatus) - { - if (!IsUnlocked) - { - // Undo secure boot exploit - Partition NvBackupPartition = GPT.GetPartition("BACKUP_BS_NV"); - if (NvBackupPartition != null) - { - // This must be a left over of a half unlocked bootloader - Partition NvPartition = GPT.GetPartition("UEFI_BS_NV"); - NvBackupPartition.Name = "UEFI_BS_NV"; - NvBackupPartition.PartitionGuid = NvPartition.PartitionGuid; - NvBackupPartition.PartitionTypeGuid = NvPartition.PartitionTypeGuid; - GPT.Partitions.Remove(NvPartition); - GPTChanged = true; - } - - PhoneInfo Info = FlashModel.ReadPhoneInfo(false); - - // We should only clear NV if there was no backup NV to be restored and the current NV contains the SB unlock. - if ((NvBackupPartition == null) && !Info.UefiSecureBootEnabled) - { - // ClearNV - Part = new FlashPart(); - Partition Target2 = GPT.GetPartition("UEFI_BS_NV"); - Part.StartSector = (UInt32)Target2.FirstSector; - Part.Stream = new MemoryStream(new byte[0x40000]); - Parts.Add(Part); - } - } - else - { - // Now add NV partition - Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); - Partition UEFI_BS_NV; - if (BACKUP_BS_NV == null) - { - BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); - Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; - Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; - BACKUP_BS_NV.Name = "BACKUP_BS_NV"; - BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); - BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); - UEFI_BS_NV = new Partition - { - Name = "UEFI_BS_NV", - Attributes = BACKUP_BS_NV.Attributes, - PartitionGuid = OriginalPartitionGuid, - PartitionTypeGuid = OriginalPartitionTypeGuid, - FirstSector = BACKUP_BS_NV.LastSector + 1 - }; - UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; - GPT.Partitions.Add(UEFI_BS_NV); - GPTChanged = true; - } - - Part = new FlashPart(); - Target = GPT.GetPartition("UEFI_BS_NV"); - Part.StartSector = (UInt32)Target.FirstSector; // GPT is prepared for 64-bit sector-offset, but flash app isn't. - Part.Stream = new SeekableStream(() => - { - var assembly = System.Reflection.Assembly.GetExecutingAssembly(); - - // Magic! - // The SB resource is a compressed version of a raw NV-variable-partition. - // In this partition the SecureBoot variable is disabled. - // It overwrites the variable in a different NV-partition than where this variable is stored usually. - // This normally leads to endless-loops when the NV-variables are enumerated. - // But the partition contains an extra hack to break out the endless loops. - var stream = assembly.GetManifestResourceStream("WPinternals.SB"); - - return new DecompressedStream(stream); - }); - Parts.Add(Part); - } - } - - if (GPTChanged) - { - GPT.Rebuild(); - Part = new FlashPart - { - StartSector = 0, - Stream = new MemoryStream(GPTChunk) - }; - Parts.Add(Part); - } - - // And then add the partitions from the archive - if (PartitionCount > 0) - { - foreach (ZipArchiveEntry Entry in Archive.Entries) - { - if (!Entry.FullName.Contains("/")) // No subfolders - { - // "MainOS.bin.gz" => "MainOS" - string PartitionName = Entry.Name; - int Pos = PartitionName.IndexOf('.'); - if (Pos >= 0) - { - PartitionName = PartitionName.Substring(0, Pos); - } - - Target = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); - if (Target != null) - { - Part = new FlashPart - { - StartSector = (UInt32)Target.FirstSector, - Stream = new SeekableStream(() => new DecompressedStream(Entry.Open()), Entry.Length), - ProgressText = "Flashing partition " + Target.Name - }; - Parts.Add(Part); - LogFile.Log("Partition name=" + PartitionName + ", startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Entry.Length / 0x200).ToString("X8"), LogType.FileOnly); - } - } - } - - Parts = Parts.OrderBy(p => p.StartSector).ToList(); - int Count = 1; - Parts.Where(p => p.ProgressText?.StartsWith("Flashing partition ") == true).ToList().ForEach((p) => - { - p.ProgressText += " (" + Count.ToString() + "/" + PartitionCount.ToString() + ")"; - Count++; - }); - - // Do actual flashing! - await LumiaV2CustomFlash(Notifier, null, false, false, Parts, true, ClearFlashingStatus, false, true, false, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure); - } - else - { - LogFile.Log("Flash failed! No valid partitions found in the archive.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "No valid partitions found in the archive"); - return; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - ExitFailure(Ex.Message, Ex is WPinternalsException ? ((WPinternalsException)Ex).SubMessage : null); - } - - LogFile.EndAction("FlashCustomROM"); - } - - internal async static Task LumiaV2FixBoot(PhoneNotifierViewModel Notifier, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) - { - LogFile.BeginAction("FixBoot"); - - LogFile.Log("Command: Fix boot after unlocking bootloader", LogType.FileAndConsole); - NokiaFlashModel FlashModel = (NokiaFlashModel)Notifier.CurrentModel; - - if (SetWorkingStatus == null) - { - SetWorkingStatus = (m, s, v, a, st) => { }; - } - - if (UpdateWorkingStatus == null) - { - UpdateWorkingStatus = (m, s, v, st) => { }; - } - - if (ExitSuccess == null) - { - ExitSuccess = (m, s) => { }; - } - - if (ExitFailure == null) - { - ExitFailure = (m, s) => { }; - } - - try - { - await SwitchModeViewModel.SwitchToWithProgress(Notifier, PhoneInterfaces.Lumia_MassStorage, (msg, sub) => SetWorkingStatus(msg, sub, null, Status: WPinternalsStatus.SwitchingMode)); - SetWorkingStatus("Applying patches...", null, null, Status: WPinternalsStatus.Patching); - App.PatchEngine.TargetPath = ((MassStorage)Notifier.CurrentModel).Drive + "\\"; - bool PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); - if (!PatchResult) - { - throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the MainOS partition of your phone. Make sure your phone runs a supported Operating System version."); - } - - LogFile.Log("Fixed bootloader", LogType.FileAndConsole); - LogFile.Log("The phone is left in Mass Storage mode", LogType.FileAndConsole); - LogFile.Log("Press and hold the power-button of the phone for at least 10 seconds to reset the phone", LogType.FileAndConsole); - ExitSuccess("Fixed bootloader!", "The phone is left in Mass Storage mode. Press and hold the power-button of the phone for at least 10 seconds to reset the phone."); - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - ExitFailure(Ex.Message, Ex is WPinternalsException ? ((WPinternalsException)Ex).SubMessage : null); - } - - LogFile.EndAction("FixBoot"); - } - - // Assumes phone with Flash protocol v2 - // Assumes phone is in flash mode - internal async static Task LumiaV2FlashPartitions(PhoneNotifierViewModel Notifier, string EFIESPPath, string MainOSPath, string DataPath, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) - { - NokiaFlashModel FlashModel = (NokiaFlashModel)Notifier.CurrentModel; - - // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. - // We need the fist sector if we want to write back the GPT. - byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); - GPT GPT = new(GPTChunk); - - Partition Target; - FlashPart Part; - List Parts = new(); - ulong MainOSOldSectorCount = 0; - ulong MainOSNewSectorCount = 0; - ulong DataOldSectorCount = 0; - ulong DataNewSectorCount = 0; - ulong FirstMainOSSector = 0; - int PartitionCount = 0; - ulong LengthInSectors; - FileInfo FileInfo; - Partition Partition; - bool IsUnlocked = false; - bool GPTChanged = false; - bool ClearFlashingStatus = true; - - if (SetWorkingStatus == null) - { - SetWorkingStatus = (m, s, v, a, st) => { }; - } - - if (UpdateWorkingStatus == null) - { - UpdateWorkingStatus = (m, s, v, st) => { }; - } - - if (ExitSuccess == null) - { - ExitSuccess = (m, s) => { }; - } - - if (ExitFailure == null) - { - ExitFailure = (m, s) => { }; - } - - SetWorkingStatus("Initializing flash...", null, null, Status: WPinternalsStatus.Initializing); - - try - { - if (EFIESPPath != null) - { - FileInfo = new FileInfo(EFIESPPath); - LengthInSectors = (ulong)FileInfo.Length / 0x200; - Partition = GPT.GetPartition("EFIESP"); - if (Partition.SizeInSectors < LengthInSectors) - { - LogFile.Log("Flash failed! Size of partition 'EFIESP' is too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Size of partition 'EFIESP' is too big."); - return; - } - PartitionCount++; - - byte[] EfiespBinary = File.ReadAllBytes(EFIESPPath); - IsUnlocked = (ByteOperations.ReadUInt32(EfiespBinary, 0x20) == (EfiespBinary.Length / 0x200 / 2)) && ByteOperations.ReadAsciiString(EfiespBinary, (UInt32)(EfiespBinary.Length / 2) + 3, 8) == "MSDOS5.0"; - if (IsUnlocked) - { - Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); - if (IsUnlockedFlag == null) - { - IsUnlockedFlag = new Partition - { - Name = "IS_UNLOCKED", - Attributes = 0, - PartitionGuid = Guid.NewGuid(), - PartitionTypeGuid = Guid.NewGuid(), - FirstSector = 0x40, - LastSector = 0x40 - }; - GPT.Partitions.Add(IsUnlockedFlag); - GPTChanged = true; - ClearFlashingStatus = false; - } - } - else - { - Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); - if (IsUnlockedFlag != null) - { - GPT.Partitions.Remove(IsUnlockedFlag); - GPTChanged = true; - ClearFlashingStatus = false; - } - } - } - - if (MainOSPath != null) - { - FileInfo = new FileInfo(MainOSPath); - LengthInSectors = (ulong)FileInfo.Length / 0x200; - Partition = GPT.GetPartition("MainOS"); - MainOSOldSectorCount = Partition.SizeInSectors; - MainOSNewSectorCount = LengthInSectors; - FirstMainOSSector = Partition.FirstSector; - PartitionCount++; - } - - if (DataPath != null) - { - FileInfo = new FileInfo(DataPath); - LengthInSectors = (ulong)FileInfo.Length / 0x200; - Partition = GPT.GetPartition("Data"); - DataOldSectorCount = Partition.SizeInSectors; - DataNewSectorCount = LengthInSectors; - PartitionCount++; - } - - if (PartitionCount > 0) - { - if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) - { - if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) - { - UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; - if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) - { - // MainOS and Data partitions need to be re-aligned! - Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); - Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); - MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; - DataPartition.FirstSector = MainOSPartition.LastSector + 1; - if ((DataPartition.FirstSector % 0x100) > 0) - { - DataPartition.FirstSector = (DataPartition.FirstSector + 0x100) / 0x100 * 0x100; - } - - DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; - - GPTChanged = true; - } - else - { - LogFile.Log("Flash failed! Sizes of partitions 'MainOS' and 'Data' together are too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); - return; - } - } - } - else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'MainOS' is too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); - return; - } - else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) - { - LogFile.Log("Flash failed! Size of partition 'Data' is too big.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "Size of partition 'Data' is too big."); - return; - } - - if (!ClearFlashingStatus) - { - if (!IsUnlocked) - { - // Undo secure boot exploit - Partition NvBackupPartition = GPT.GetPartition("BACKUP_BS_NV"); - if (NvBackupPartition != null) - { - // This must be a left over of a half unlocked bootloader - Partition NvPartition = GPT.GetPartition("UEFI_BS_NV"); - NvBackupPartition.Name = "UEFI_BS_NV"; - NvBackupPartition.PartitionGuid = NvPartition.PartitionGuid; - NvBackupPartition.PartitionTypeGuid = NvPartition.PartitionTypeGuid; - GPT.Partitions.Remove(NvPartition); - GPTChanged = true; - } - - PhoneInfo Info = FlashModel.ReadPhoneInfo(false); - - // We should only clear NV if there was no backup NV to be restored and the current NV contains the SB unlock. - if ((NvBackupPartition == null) && !Info.UefiSecureBootEnabled) - { - // ClearNV - Part = new FlashPart(); - Partition Target2 = GPT.GetPartition("UEFI_BS_NV"); - Part.StartSector = (UInt32)Target2.FirstSector; - Part.Stream = new MemoryStream(new byte[0x40000]); - Parts.Add(Part); - } - } - else - { - // Now add NV partition - Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); - Partition UEFI_BS_NV; - if (BACKUP_BS_NV == null) - { - BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); - Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; - Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; - BACKUP_BS_NV.Name = "BACKUP_BS_NV"; - BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); - BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); - UEFI_BS_NV = new Partition - { - Name = "UEFI_BS_NV", - Attributes = BACKUP_BS_NV.Attributes, - PartitionGuid = OriginalPartitionGuid, - PartitionTypeGuid = OriginalPartitionTypeGuid, - FirstSector = BACKUP_BS_NV.LastSector + 1 - }; - UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; - GPT.Partitions.Add(UEFI_BS_NV); - GPTChanged = true; - } - - Part = new FlashPart(); - Target = GPT.GetPartition("UEFI_BS_NV"); - Part.StartSector = (UInt32)Target.FirstSector; // GPT is prepared for 64-bit sector-offset, but flash app isn't. - Part.Stream = new SeekableStream(() => - { - var assembly = System.Reflection.Assembly.GetExecutingAssembly(); - - // Magic! - // The SB resource is a compressed version of a raw NV-variable-partition. - // In this partition the SecureBoot variable is disabled. - // It overwrites the variable in a different NV-partition than where this variable is stored usually. - // This normally leads to endless-loops when the NV-variables are enumerated. - // But the partition contains an extra hack to break out the endless loops. - var stream = assembly.GetManifestResourceStream("WPinternals.SB"); - - return new DecompressedStream(stream); - }); - Parts.Add(Part); - } - } - - if (GPTChanged) - { - GPT.Rebuild(); - Part = new FlashPart - { - StartSector = 0, - Stream = new MemoryStream(GPTChunk) - }; - Parts.Add(Part); - } - - int Count = 0; - - Target = GPT.Partitions.Find(p => string.Equals(p.Name, "EFIESP", StringComparison.CurrentCultureIgnoreCase)); - if ((EFIESPPath != null) && (Target != null)) - { - Count++; - Part = new FlashPart - { - StartSector = (UInt32)Target.FirstSector, - Stream = new FileStream(EFIESPPath, FileMode.Open), - ProgressText = "Flashing partition EFIESP (" + Count.ToString() + " / " + PartitionCount.ToString() + ")" - }; - Parts.Add(Part); - LogFile.Log("Partition name=EFIESP, startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Part.Stream.Length / 0x200).ToString("X8"), LogType.FileOnly); - } - - Target = GPT.Partitions.Find(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); - if ((MainOSPath != null) && (Target != null)) - { - Count++; - Part = new FlashPart - { - StartSector = (UInt32)Target.FirstSector, - Stream = new FileStream(MainOSPath, FileMode.Open), - ProgressText = "Flashing partition MainOS (" + Count.ToString() + " / " + PartitionCount.ToString() + ")" - }; - Parts.Add(Part); - LogFile.Log("Partition name=MainOS, startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Part.Stream.Length / 0x200).ToString("X8"), LogType.FileOnly); - } - - Target = GPT.Partitions.Find(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); - if ((DataPath != null) && (Target != null)) - { - Count++; - Part = new FlashPart - { - StartSector = (UInt32)Target.FirstSector, - Stream = new FileStream(DataPath, FileMode.Open), - ProgressText = "Flashing partition Data (" + Count.ToString() + " / " + PartitionCount.ToString() + ")" - }; - Parts.Add(Part); - LogFile.Log("Partition name=Data, startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Part.Stream.Length / 0x200).ToString("X8"), LogType.FileOnly); - } - - // Do actual flashing! - await LumiaV2CustomFlash(Notifier, null, false, false, Parts, true, ClearFlashingStatus, false, true, false, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure); - } - else - { - LogFile.Log("Flash failed! No valid partitions found in the archive.", LogType.FileAndConsole); - ExitFailure("Flash failed!", "No valid partitions found in the archive"); - return; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - ExitFailure(Ex.Message, Ex is WPinternalsException ? ((WPinternalsException)Ex).SubMessage : null); - return; - } - } - } - - internal static class UefiMemorySim - { - internal const UInt32 PageSize = 0x1000; - - private static UInt32 CurrentBufferSize = 0; - public static byte[] Buffer; - private static readonly List Allocations = new(); - private static readonly List FreeMemRanges = new(); - - public static UInt32 RoundUpToPages(UInt32 Size) - { - UInt32 Result = Size + 0x18; - if ((Size % PageSize) != 0) - { - Size = ((Size / PageSize) + 1) * PageSize; - } - - return Result; - } - - public static void Reset() - { - CurrentBufferSize = 0; - Buffer = null; - Allocations.Clear(); - FreeMemRanges.Clear(); - } - - private static void ExtendBuffer(UInt32 Size) - { - byte[] NewBuffer = new byte[CurrentBufferSize + Size]; - if (CurrentBufferSize > 0) - { - System.Buffer.BlockCopy(Buffer, 0, NewBuffer, (int)Size, (int)CurrentBufferSize); - } - - foreach (Allocation CurrentAllocation in Allocations) - { - CurrentAllocation.TotalStart += Size; - CurrentAllocation.TotalEnd += Size; - CurrentAllocation.HeadStart += Size; - CurrentAllocation.HeadEnd += Size; - CurrentAllocation.ContentStart += Size; - CurrentAllocation.ContentEnd += Size; - CurrentAllocation.TailStart += Size; - CurrentAllocation.TailEnd += Size; - } - foreach (FreeMemRange CurrentRange in FreeMemRanges) - { - CurrentRange.Start += Size; - CurrentRange.End += Size; - } - CurrentBufferSize += Size; - Buffer = NewBuffer; - } - - internal static Allocation AllocatePages(UInt32 Size) - { - Allocation NewAllocation = null; - - UInt32 TotalSize = Size; - - if ((TotalSize % PageSize) != 0) - { - throw new NotSupportedException("Wrong allocation size"); - } - else - { - for (int i = FreeMemRanges.Count - 1; i >= 0; i--) - { - if (FreeMemRanges[i].Size >= TotalSize) - { - NewAllocation = new Allocation - { - TotalStart = FreeMemRanges[i].End - TotalSize + 1 - }; - - if (FreeMemRanges[i].Size == TotalSize) - { - FreeMemRanges.RemoveAt(i); - } - else - { - FreeMemRanges[i].End -= TotalSize; - } - - break; - } - } - - if (NewAllocation == null) - { - uint FreeBuffer = Allocations.Count > 0 ? Allocations[0].TotalStart : CurrentBufferSize; - if (FreeBuffer < TotalSize) - { - ExtendBuffer(TotalSize - FreeBuffer); - } - - NewAllocation = new Allocation(); - - if (Allocations.Count > 0) - { - NewAllocation.TotalStart = Allocations[0].TotalStart - TotalSize; - } - else - { - FreeBuffer = CurrentBufferSize - TotalSize; - } - } - - bool Added = false; - for (int i = 0; i < Allocations.Count; i++) - { - if (NewAllocation.TotalStart < Allocations[i].TotalStart) - { - Allocations.Insert(i, NewAllocation); - Added = true; - break; - } - } - if (!Added) - { - Allocations.Add(NewAllocation); - } - } - - return NewAllocation; - } - - internal static Allocation AllocatePool(UInt32 Size) - { - Allocation NewAllocation = null; - - UInt32 TotalSize = Size + 24; - - if (TotalSize < PageSize) - { - throw new NotSupportedException("Allocation of small memory area's is not supported by this UEFI memory management simulator"); - } - else - { - if ((TotalSize % PageSize) != 0) - { - TotalSize = ((TotalSize / PageSize) + 1) * PageSize; - } - - for (int i = FreeMemRanges.Count - 1; i >= 0; i--) - { - if (FreeMemRanges[i].Size >= TotalSize) - { - NewAllocation = new Allocation - { - TotalStart = FreeMemRanges[i].End - TotalSize + 1 - }; - - if (FreeMemRanges[i].Size == TotalSize) - { - FreeMemRanges.RemoveAt(i); - } - else - { - FreeMemRanges[i].End -= TotalSize; - } - - break; - } - } - - if (NewAllocation == null) - { - uint FreeBuffer = Allocations.Count > 0 ? Allocations[0].TotalStart : CurrentBufferSize; - if (FreeBuffer < TotalSize) - { - ExtendBuffer(TotalSize - FreeBuffer); - } - - NewAllocation = new Allocation(); - - if (Allocations.Count > 0) - { - NewAllocation.TotalStart = Allocations[0].TotalStart - TotalSize; - } - else - { - FreeBuffer = CurrentBufferSize - TotalSize; - } - } - - NewAllocation.TotalEnd = NewAllocation.TotalStart + TotalSize - 1; - NewAllocation.HeadStart = NewAllocation.TotalStart; - NewAllocation.HeadEnd = NewAllocation.HeadStart + 16 - 1; - NewAllocation.ContentStart = NewAllocation.HeadEnd + 1; - NewAllocation.ContentEnd = NewAllocation.ContentStart + Size - 1; - NewAllocation.TailStart = NewAllocation.ContentEnd + 1; - NewAllocation.TailEnd = NewAllocation.TailStart + 8 - 1; - - ByteOperations.WriteAsciiString(Buffer, NewAllocation.HeadStart + 0x00, "phd0"); - - // Correct value here is: Size + 24 - // Wrong value is: TotalSize - // Having correct value avoids memory errors and phone can reboot normally, but NV vars might be written (and that will overwrite the NV vars we wrote ourselves). - // Wrong value will make phone reboot to emergency boot and it makes the phone crash when you want to flash in multiple phases, but it will avoid writing NV vars. - ByteOperations.WriteUInt32(Buffer, NewAllocation.HeadStart + 0x04, Size + 24); - - ByteOperations.WriteUInt32(Buffer, NewAllocation.HeadStart + 0x08, 0x04); // EfiBootServicesData = 0x04 - ByteOperations.WriteUInt32(Buffer, NewAllocation.HeadStart + 0x0C, 0x00); // Reserved - - ByteOperations.WriteAsciiString(Buffer, NewAllocation.TailStart + 0x00, "ptal"); - - // Correct value here is: Size + 24 - // Wrong value is: TotalSize - // Having correct value avoids memory errors and phone can reboot normally, but NV vars might be written (and that will overwrite the NV vars we wrote ourselves). - // Wrong value will make phone reboot to emergency boot and it makes the phone crash when you want to flash in multiple phases, but it will avoid writing NV vars. - ByteOperations.WriteUInt32(Buffer, NewAllocation.TailStart + 0x04, Size + 24); - - bool Added = false; - for (int i = 0; i < Allocations.Count; i++) - { - if (NewAllocation.TotalStart < Allocations[i].TotalStart) - { - Allocations.Insert(i, NewAllocation); - Added = true; - break; - } - } - if (!Added) - { - Allocations.Add(NewAllocation); - } - } - - return NewAllocation; - } - - internal static void FreePool(Allocation Allocation) - { - if (Allocations.Contains(Allocation)) - { - Allocations.Remove(Allocation); - - if (Allocations.Count == 0) - { - FreeMemRanges.Clear(); - } - else - { - FreeMemRange NewFreeRange = new(); - NewFreeRange.Start = Allocation.TotalStart; - NewFreeRange.End = Allocation.TotalEnd; - - bool Added = false; - int i; - for (i = 0; i < FreeMemRanges.Count; i++) - { - if (NewFreeRange.Start < FreeMemRanges[i].Start) - { - FreeMemRanges.Insert(i, NewFreeRange); - Added = true; - break; - } - } - if (!Added) - { - FreeMemRanges.Add(NewFreeRange); - i = FreeMemRanges.Count; - } - - if ((i > 0) && (FreeMemRanges[i].Start == (FreeMemRanges[i - 1].End + 1))) - { - FreeMemRanges[i - 1].End = FreeMemRanges[i].End; - FreeMemRanges.RemoveAt(i); - i--; - } - - if ((i < (FreeMemRanges.Count - 1)) && (FreeMemRanges[i].End == (FreeMemRanges[i - 1].Start - 1))) - { - FreeMemRanges[i].End = FreeMemRanges[i + 1].End; - FreeMemRanges.RemoveAt(i + 1); - } - - if ((Allocations.Count > 0) && (FreeMemRanges[i].Start < Allocations[0].TotalStart)) - { - FreeMemRanges.RemoveAt(i); - } - } - } - } - } - - internal class Allocation - { - public UInt32 TotalStart; - public UInt32 TotalEnd; - public UInt32 ContentStart; - public UInt32 ContentEnd; - public UInt32 HeadStart; - public UInt32 HeadEnd; - public UInt32 TailStart; - public UInt32 TailEnd; - - public UInt32 TotalSize - { - get - { - return TotalEnd - TotalStart + 1; - } - } - - public UInt32 ContentSize - { - get - { - return ContentEnd - ContentStart + 1; - } - } - - public void CopyFromThisAllocation(UInt32 ContentOffset, UInt32 Size, byte[] Destination, UInt32 DestinationOffset) - { - Buffer.BlockCopy(UefiMemorySim.Buffer, (int)(ContentStart + ContentOffset), Destination, (int)DestinationOffset, (int)Size); - } - - public void CopyToThisAllocation(byte[] Source, UInt32 SourceOffset, UInt32 Size, UInt32 ContentOffset) - { - Buffer.BlockCopy(Source, (int)SourceOffset, UefiMemorySim.Buffer, (int)(ContentStart + ContentOffset), (int)Size); - } - } - - internal class FreeMemRange - { - public UInt32 Start; - public UInt32 End; - - public UInt32 Size - { - get - { - return End - Start + 1; - } - } - } - - internal class FlashPart - { - public string ProgressText; - public UInt32 StartSector; - public Stream Stream; - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; + +namespace WPinternals +{ + internal class LumiaV2UnlockBootViewModel : ContextViewModel + { + internal static async Task LumiaV2FindFlashingProfile(PhoneNotifierViewModel Notifier, string FFUPath, bool DoResetFirst = true, bool Experimental = false, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) + { + if (SetWorkingStatus == null) + { + SetWorkingStatus = (m, s, v, a, st) => { }; + } + + if (UpdateWorkingStatus == null) + { + UpdateWorkingStatus = (m, s, v, st) => { }; + } + + if (ExitSuccess == null) + { + ExitSuccess = (m, s) => { }; + } + + if (ExitFailure == null) + { + ExitFailure = (m, s) => { }; + } + + LogFile.BeginAction("FindFlashingProfile"); + try + { + LogFile.Log("Find Flashing Profile", LogType.FileAndConsole); + + NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + + PhoneInfo Info; + if (DoResetFirst) + { + // The phone will be reset before flashing, so we have the opportunity to get some more info from the phone + Info = FlashModel.ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + + string FfuFirmware = null; + if (FFUPath != null) + { + FFU FFU = new(FFUPath); + FfuFirmware = FFU.GetFirmwareVersion(); + } + FlashProfile Profile = App.Config.GetProfile(Info.PlatformID, Info.Firmware, FfuFirmware); + + if (Profile != null) + { + LogFile.Log("Flashing Profile already present for this phone", LogType.FileAndConsole); + return; + } + } + else + { + Info = FlashModel.ReadPhoneInfo(ExtendedInfo: false); + } + + SetWorkingStatus("Scanning for flashing-profile", "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", null, Status: WPinternalsStatus.Scanning); + + await LumiaV2CustomFlash(Notifier, FFUPath, false, !Info.IsBootloaderSecure, null, DoResetFirst, Experimental: Experimental, SetWorkingStatus: + (m, s, v, a, st) => + { + if (st == WPinternalsStatus.SwitchingMode) + { + SetWorkingStatus(m, s, v, a, st); + } + }, + UpdateWorkingStatus: + (m, s, v, st) => + { + if (st == WPinternalsStatus.SwitchingMode) + { + UpdateWorkingStatus(m, s, v, st); + } + }, + ExitSuccess: ExitSuccess, ExitFailure: ExitFailure); + + LogFile.Log("Flashing profile found!", LogType.FileAndConsole); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("FindFlashingProfile"); + } + } + + internal static async Task LumiaV2EnableTestSigning(System.Threading.SynchronizationContext UIContext, string FFUPath, bool DoResetFirst = true) + { + LogFile.BeginAction("EnableTestSigning"); + try + { + LogFile.Log("Command: Enable testsigning", LogType.FileAndConsole); + PhoneNotifierViewModel Notifier = new(); + UIContext.Send(s => Notifier.Start(), null); + NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + + List Parts = new(); + FlashPart Part; + + // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. + // We need the fist sector if we want to write back the GPT. + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); + GPT GPT = new(GPTChunk); + bool GPTChanged = false; + + Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); + Partition UEFI_BS_NV; + if (BACKUP_BS_NV == null) + { + BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); + Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; + Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; + BACKUP_BS_NV.Name = "BACKUP_BS_NV"; + BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); + BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); + UEFI_BS_NV = new Partition + { + Name = "UEFI_BS_NV", + Attributes = BACKUP_BS_NV.Attributes, + PartitionGuid = OriginalPartitionGuid, + PartitionTypeGuid = OriginalPartitionTypeGuid, + FirstSector = BACKUP_BS_NV.LastSector + 1 + }; + UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; + GPT.Partitions.Add(UEFI_BS_NV); + GPTChanged = true; + } + if (GPTChanged) + { + GPT.Rebuild(); + Part = new FlashPart + { + StartSector = 0, + Stream = new MemoryStream(GPTChunk) + }; + Parts.Add(Part); + } + + // This code was used to compress the partition to an embedded resource: + // + // byte[] sbpart = System.IO.File.ReadAllBytes(@"C:\Windows Phone 8\Sources\WPInternals\SB.Original.bin"); + // System.IO.FileStream s = new System.IO.FileStream(@"C:\Windows Phone 8\Sources\WPInternals\SB", System.IO.FileMode.Create, System.IO.FileAccess.Write); + // CompressedStream Out = new CompressedStream(s, (ulong)sbpart.Length); + // Out.Write(sbpart, 0, sbpart.Length); + // Out.Close(); + // s.Close(); + + Part = new FlashPart(); + Partition TargetPartition = GPT.GetPartition("UEFI_BS_NV"); + Part.StartSector = (UInt32)TargetPartition.FirstSector; // GPT is prepared for 64-bit sector-offset, but flash app isn't. + Part.Stream = new SeekableStream(() => + { + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + + // Magic! + // The SB resource is a compressed version of a raw NV-variable-partition. + // In this partition the SecureBoot variable is disabled. + // It overwrites the variable in a different NV-partition than where this variable is stored usually. + // This normally leads to endless-loops when the NV-variables are enumerated. + // But the partition contains an extra hack to break out the endless loops. + var stream = assembly.GetManifestResourceStream("WPinternals.SB"); + + return new DecompressedStream(stream); + }); + Parts.Add(Part); + + await LumiaV2CustomFlash(Notifier, FFUPath, false, false, Parts, DoResetFirst, ClearFlashingStatusAtEnd: false); + + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("EnableTestSigning"); + } + } + + internal static async Task LumiaV2SwitchToMassStorageMode(PhoneNotifierViewModel Notifier, string FFUPath, bool DoResetFirst = true) + { + // If there is no phone connected yet, we wait here for the phone to connect. + // Because it could be connecting in mass storage mode. + // So we dont want to let SwitchTo() wait for it, because it might already be trying to switch to flash mode, which is not possible and not necessary at that point. + if (Notifier.CurrentInterface == null) + { + LogFile.Log("Waiting for phone to connect...", LogType.FileAndConsole); + await Notifier.WaitForArrival(); + } + + if (Notifier.CurrentInterface == PhoneInterfaces.Lumia_MassStorage) + { + LogFile.Log("Phone is already in Mass Storage Mode", LogType.FileAndConsole); + return ((MassStorage)Notifier.CurrentModel).Drive; + } + + NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + if (DoResetFirst) + { + // The phone will be reset before flashing, so we have the opportunity to get some more info from the phone + PhoneInfo Info = FlashModel.ReadPhoneInfo(); + Info.Log(LogType.ConsoleOnly); + } + + await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_MassStorage); + + MassStorage Storage = null; + if (Notifier.CurrentModel is MassStorage) + { + Storage = (MassStorage)Notifier.CurrentModel; + } + + if (Storage == null) + { + throw new WPinternalsException("Failed to switch to Mass Storage Mode"); + } + + return Storage.Drive; + } + + internal static async Task LumiaV2ClearNV(System.Threading.SynchronizationContext UIContext, string FFUPath, bool DoResetFirst = true) + { + LogFile.BeginAction("ClearNV"); + try + { + LogFile.Log("Command: Clear NV", LogType.FileAndConsole); + PhoneNotifierViewModel Notifier = new(); + UIContext.Send(s => Notifier.Start(), null); + NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + List Parts = new(); + + // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. + // We need the fist sector if we want to write back the GPT. + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); + GPT GPT = new(GPTChunk); + bool GPTChanged = false; + Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); + Partition UEFI_BS_NV; + if (BACKUP_BS_NV == null) + { + BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); + Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; + Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; + BACKUP_BS_NV.Name = "BACKUP_BS_NV"; + BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); + BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); + UEFI_BS_NV = new Partition + { + Name = "UEFI_BS_NV", + Attributes = BACKUP_BS_NV.Attributes, + PartitionGuid = OriginalPartitionGuid, + PartitionTypeGuid = OriginalPartitionTypeGuid, + FirstSector = BACKUP_BS_NV.LastSector + 1 + }; + UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; + GPT.Partitions.Add(UEFI_BS_NV); + GPTChanged = true; + } + if (GPTChanged) + { + GPT.Rebuild(); + FlashPart Part = new(); + Part.StartSector = 0; + Part.Stream = new MemoryStream(GPTChunk); + Parts.Add(Part); + } + + using (MemoryStream Space = new(new byte[0x40000])) + { + Partition Target = GPT.GetPartition("UEFI_BS_NV"); + Parts.Add(new FlashPart() { StartSector = (uint)Target.FirstSector, Stream = Space }); + + await LumiaV2CustomFlash(Notifier, FFUPath, false, false, Parts, DoResetFirst, ClearFlashingStatusAtEnd: false); + } + + LogFile.Log("NV successfully cleared!", LogType.FileAndConsole); + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("ClearNV"); + } + } + + internal static async Task LumiaV2FlashPartition(System.Threading.SynchronizationContext UIContext, string FFUPath, string PartitionName, string PartitionPath, bool DoResetFirst = true) + { + LogFile.BeginAction("FlashPartition"); + try + { + LogFile.Log("Command: Flash Partition", LogType.FileAndConsole); + LogFile.Log("Partition name: " + PartitionName, LogType.FileAndConsole); + LogFile.Log("Partition file: " + PartitionPath, LogType.FileAndConsole); + if (FFUPath != null) + { + LogFile.Log("Profile FFU file: " + FFUPath, LogType.FileAndConsole); + } + + PhoneNotifierViewModel Notifier = new(); + UIContext.Send(s => Notifier.Start(), null); + + NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + + PhoneInfo Info = FlashModel.ReadPhoneInfo(); + + // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. + // We need the fist sector if we want to write back the GPT. + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); + GPT GPT = new(GPTChunk); + + Partition TargetPartition = GPT.GetPartition(PartitionName); + if (TargetPartition == null) + { + throw new WPinternalsException("Target partition not found!", "Couldn't find \"" + PartitionName + "\" from the device GPT."); + } + + LogFile.Log("Target-partition found at sector: 0x" + TargetPartition.FirstSector.ToString("X8") + " - 0x" + TargetPartition.LastSector.ToString("X8"), LogType.FileAndConsole); + + bool IsUnlocked = false; + bool GPTChanged = false; + List Parts = new(); + FlashPart Part; + if (string.Equals(PartitionName, "EFIESP", StringComparison.CurrentCultureIgnoreCase)) + { + byte[] EfiespBinary = File.ReadAllBytes(PartitionPath); + IsUnlocked = (ByteOperations.ReadUInt32(EfiespBinary, 0x20) == (EfiespBinary.Length / 0x200 / 2)) && ByteOperations.ReadAsciiString(EfiespBinary, (UInt32)(EfiespBinary.Length / 2) + 3, 8) == "MSDOS5.0"; + + if (IsUnlocked) + { + Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); + if (IsUnlockedFlag == null) + { + IsUnlockedFlag = new Partition + { + Name = "IS_UNLOCKED", + Attributes = 0, + PartitionGuid = Guid.NewGuid(), + PartitionTypeGuid = Guid.NewGuid(), + FirstSector = 0x40, + LastSector = 0x40 + }; + GPT.Partitions.Add(IsUnlockedFlag); + GPTChanged = true; + } + } + else + { + Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); + if (IsUnlockedFlag != null) + { + GPT.Partitions.Remove(IsUnlockedFlag); + GPTChanged = true; + } + } + + if (GPTChanged) + { + GPT.Rebuild(); + Part = new FlashPart + { + StartSector = 0, + Stream = new MemoryStream(GPTChunk) + }; + Parts.Add(Part); + } + } + + using (FileStream Stream = new(PartitionPath, FileMode.Open)) + { + if ((UInt64)Stream.Length != (TargetPartition.SizeInSectors * 0x200)) + { + throw new WPinternalsException("Raw partition has wrong size. Size = 0x" + Stream.Length.ToString("X8") + ". Expected size = 0x" + (TargetPartition.SizeInSectors * 0x200).ToString("X8")); + } + + Part = new FlashPart + { + StartSector = (UInt32)TargetPartition.FirstSector, + Stream = Stream + }; + Parts.Add(Part); + await LumiaV2CustomFlash(Notifier, FFUPath, false, false, Parts, DoResetFirst, !string.Equals(PartitionName, "UEFI_BS_NV", StringComparison.CurrentCultureIgnoreCase)); + } + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("FlashPartition"); + } + } + + internal static async Task LumiaV2FlashRaw(System.Threading.SynchronizationContext UIContext, UInt64 StartSector, string DataPath, string FFUPath, bool DoResetFirst = true) + { + LogFile.BeginAction("FlashRaw"); + try + { + LogFile.Log("Command: Flash Raw", LogType.FileAndConsole); + LogFile.Log("Start sector: 0x" + StartSector.ToString("X16"), LogType.FileAndConsole); + LogFile.Log("Data file: " + DataPath, LogType.FileAndConsole); + if (FFUPath != null) + { + LogFile.Log("FFU file: " + FFUPath, LogType.FileAndConsole); + } + + PhoneNotifierViewModel Notifier = new(); + UIContext.Send(s => Notifier.Start(), null); + + NokiaFlashModel FlashModel = (NokiaFlashModel)await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash); + + PhoneInfo Info = FlashModel.ReadPhoneInfo(); + + byte[] Data = File.ReadAllBytes(DataPath); + + await LumiaV2CustomFlash(Notifier, FFUPath, false, false, (UInt32)StartSector, Data, DoResetFirst); + Notifier.Stop(); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + } + finally + { + LogFile.EndAction("FlashRaw"); + } + } + + internal async static Task LumiaV2CustomFlash(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, UInt32 StartSector, byte[] Data, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false) //, string LoaderPath = null) + { + using MemoryStream Stream = new(Data); + FlashPart Part = new() { StartSector = StartSector, Stream = Stream }; + List Parts = new(); + Parts.Add(Part); + await LumiaV2CustomFlash(Notifier, FFUPath, PerformFullFlashFirst, SkipWrite, Parts, DoResetFirst, ClearFlashingStatusAtEnd, CheckSectorAlignment, ShowProgress, Experimental); + } + + internal async static Task LumiaV2CustomFlash(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, UInt32 StartSector, Stream Data, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false) //, string LoaderPath = null) + { + FlashPart Part = new() { StartSector = StartSector, Stream = Data }; + List Parts = new(); + Parts.Add(Part); + await LumiaV2CustomFlash(Notifier, FFUPath, PerformFullFlashFirst, SkipWrite, Parts, DoResetFirst, ClearFlashingStatusAtEnd, CheckSectorAlignment, ShowProgress, Experimental); + } + + internal async static Task LumiaV2CustomFlash(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, List FlashParts, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null, string ProgrammerPath = null) + { + NokiaFlashModel Model = (NokiaFlashModel)Notifier.CurrentModel; + PhoneInfo Info = Model.ReadPhoneInfo(); + + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Model, 131072u); + + GPT GPT = new(GPTChunk); + + Partition UefiBSNV = GPT.GetPartition("UEFI_BS_NV"); + + bool UseOlderExploit = Info.UefiSecureBootEnabled; + + if (!UseOlderExploit && ClearFlashingStatusAtEnd) + { + if (FlashParts == null) + { + UseOlderExploit = true; + } + else + { + foreach (var part in FlashParts) + { + if (part.StartSector >= UefiBSNV.FirstSector && part.StartSector <= UefiBSNV.LastSector) + { + UseOlderExploit = true; + break; + } + } + } + } + + if (UseOlderExploit || !ClearFlashingStatusAtEnd) + { + await LumiaV2CustomFlashInternal(Notifier, FFUPath, PerformFullFlashFirst, SkipWrite, FlashParts, DoResetFirst, ClearFlashingStatusAtEnd, CheckSectorAlignment, ShowProgress, Experimental, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure, ProgrammerPath); + } + else + { + await LumiaV3FlashRomViewModel.LumiaV3CustomFlash(Notifier, FlashParts, CheckSectorAlignment, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure); + } + } + + // Magic! + internal async static Task LumiaV2CustomFlashInternal(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, List FlashParts, bool DoResetFirst = true, bool ClearFlashingStatusAtEnd = true, bool CheckSectorAlignment = true, bool ShowProgress = true, bool Experimental = false, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null, string ProgrammerPath = null) //, string LoaderPath = null) + { + // Both SecurityHeader and StoreHeader need to be modified. + // Those should both not fall in a memory-gap to allow modification. + // The partial FFU header must be allocated in front of those headers, so the size of the partial header must be at least the size of the the SecurityHeader. + // Hashes take more space than descriptors, so the SecurityHeader will always be the biggest. + + bool AutoEmergencyReset = true; + bool Timeout; + + if (SetWorkingStatus == null) + { + SetWorkingStatus = (m, s, v, a, st) => { }; + } + + if (UpdateWorkingStatus == null) + { + UpdateWorkingStatus = (m, s, v, st) => { }; + } + + if (ExitSuccess == null) + { + ExitSuccess = (m, s) => { }; + } + + if (ExitFailure == null) + { + ExitFailure = (m, s) => { }; + } + + NokiaFlashModel Model = (NokiaFlashModel)Notifier.CurrentModel; + PhoneInfo Info = Model.ReadPhoneInfo(); + + string Type = Info.Type; + if (ProgrammerPath == null) + { + ProgrammerPath = GetProgrammerPath(Info.RKH, Type); + if (ProgrammerPath == null) + { + LogFile.Log("WARNING: No emergency programmer file found. Finding flash profile and rebooting phone may take a long time!", LogType.FileAndConsole); + } + } + List FFUs = null; + FlashProfile Profile; + if (FFUPath == null) + { + // Try to find an FFU from the repository for which there is also a known flashing profile + FFUs = App.Config.FFURepository.Where(e => Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists()).ToList(); + foreach (FFUEntry CurrentEntry in FFUs) + { + Profile = App.Config.GetProfile(Info.PlatformID, Info.Firmware, CurrentEntry.FirmwareVersion); + if (Profile != null) + { + FFUPath = CurrentEntry.Path; + break; + } + } + } + + if (FFUPath == null) + { + // Try to find any FFU with matching PlatformID in the repository + if (FFUs.Count > 0) + { + FFUPath = FFUs[0].Path; + } + } + + if (FFUPath == null) + { + throw new WPinternalsException("No valid profile FFU found in repository", "You can download necessary files in the "Download" section"); + } + + FFU FFU = new(FFUPath); + UInt32 UpdateType = ByteOperations.ReadUInt32(FFU.StoreHeader, 0); + if (UpdateType != 0) + { + throw new WPinternalsException("Only Full Flash images supported", "The provided FFU file reports that it doesn't support Full Flash updates, but may support something else such as Partial Flash updates. This is not supported."); + } + + if (FlashParts != null) + { + foreach (FlashPart Part in FlashParts) + { + if (Part.Stream == null) + { + throw new ArgumentException("Stream is null"); + } + + if (!Part.Stream.CanSeek) + { + throw new ArgumentException("Streams must be seekable"); + } + + if ((Part.StartSector * 0x200 % FFU.ChunkSize) != 0) + { + throw new ArgumentException("Invalid StartSector alignment"); + } + + if (CheckSectorAlignment && (Part.Stream.Length % FFU.ChunkSize) != 0) + { + throw new ArgumentException("Invalid Data length"); + } + } + } + + if ((Info.SecureFfuSupportedProtocolMask & ((ushort)FfuProtocol.ProtocolSyncV2)) == 0) // Exploit needs protocol v2 -> This check is not conclusive, because old phones also report support for this protocol, although it is really not supported. + { + throw new WPinternalsException("Flash failed!", "Protocols not supported. The phone reports that it does not support the Protocol Sync V2."); + } + + if (Info.FlashAppProtocolVersionMajor < 2) // Old phones do not support the hack. These phones have Flash protocol 1.x. + { + throw new WPinternalsException("Flash failed!", "Protocols not supported. The phone reports that Flash App communication protocol is lower than 2. Reported version by the phone: " + Info.FlashAppProtocolVersionMajor + "."); + } + + UEFI UEFI = new(FFU.GetPartition("UEFI")); + string BootMgrName = UEFI.EFIs.First(efi => efi.Name?.Contains("BootMgrApp") == true).Name; + UInt32 EstimatedSizeOfMemGap = (UInt32)UEFI.GetFile(BootMgrName).Length; + byte Options = 0; + if (SkipWrite) + { + Options = (byte)FlashOptions.SkipWrite; + } + + if (!Info.IsBootloaderSecure) + { + Options = (byte)((FlashOptions)Options | FlashOptions.SkipSignatureCheck); + } + + // Gap fill calculation: + // About 0x18000 of the gap is used for other purposes. + // Then round down to fill up the rest of the space, to make sure the memory for the headers is not allocated in a gap. + UInt32 EstimatedGapFill = FFU.RoundDownToChunks(EstimatedSizeOfMemGap - 0x18000); + + UInt32 MaximumGapFill; + int MaximumAttempts; + + if (!Experimental) + { + MaximumGapFill = FFU.RoundUpToChunks(2 * EstimatedSizeOfMemGap); + MaximumAttempts = (int)(((MaximumGapFill / FFU.ChunkSize) + 1) * 4); + } + else + { + MaximumGapFill = FFU.RoundUpToChunks(4 * EstimatedSizeOfMemGap); + MaximumAttempts = (int)(((MaximumGapFill / FFU.ChunkSize) + 1) * 8); + } + + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Model, (UInt32)FFU.ChunkSize); + + // Start with a reset + if (DoResetFirst) + { + SetWorkingStatus("Initializing flash...", "Rebooting phone", null, Status: WPinternalsStatus.Initializing); + + // When in flash mode, it is not possible to reboot straight to flash. + // Reboot and catch the phone in bootloader mode and then switch to flash context + Model.ResetPhone(); + + #region Properly recover from reset - many phones respond differently + + Timeout = false; + try + { + await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); + } + catch (TimeoutException) + { + Timeout = true; + } + if ((Notifier.CurrentInterface == null) && (!AutoEmergencyReset || Timeout)) + { + AutoEmergencyReset = false; + + if (Timeout) + { + LogFile.Log("The phone is not responding", LogType.ConsoleOnly); + LogFile.Log("It might be in emergency mode, while you have no matching driver installed", LogType.ConsoleOnly); + } + LogFile.Log("To continue the unlock-sequence, the phone needs to be rebooted", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", (Timeout ? "The phone is not responding. It might be in emergency mode, while you have no matching driver installed. " : "") + "To continue the unlock-sequence, the phone needs to be rebooted. Keep the phone connected to the PC. The unlock-sequence will resume automatically.", null, false, WPinternalsStatus.WaitingForManualReset); + + await Notifier.WaitForRemoval(); + + UpdateWorkingStatus("Initializing flash...", null, null); + + await Notifier.WaitForArrival(); + } + if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) + { + bool FailedToStartProgrammer = false; + if (ProgrammerPath != null) + { + QualcommSahara Sahara = new((QualcommSerial)Notifier.CurrentModel); + try + { + await Sahara.Reset(ProgrammerPath); + await Notifier.WaitForArrival(); + } + catch (BadConnectionException) + { + FailedToStartProgrammer = true; + } + catch (Exception ex) + { + LogFile.Log("An unexpected error happened", LogType.FileAndConsole); + LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); + FailedToStartProgrammer = true; + } + } + + if (ProgrammerPath == null || FailedToStartProgrammer) + { + ((QualcommSerial)Notifier.CurrentModel).Close(); // Prevent "Resource in use"; + + Timeout = false; + if (AutoEmergencyReset) + { + try + { + await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); + } + catch (TimeoutException) + { + Timeout = true; + } + catch (Exception ex) + { + LogFile.Log("An unexpected error happened", LogType.FileAndConsole); + LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); + FailedToStartProgrammer = true; + } + } + if (!AutoEmergencyReset || Timeout) + { + AutoEmergencyReset = false; + + if (!FailedToStartProgrammer) + { + LogFile.Log("The phone is in emergency mode and you didn't provide an emergency programmer", LogType.ConsoleOnly); + LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("To prevent this, provide an emergency programmer next time you will unlock a bootloader", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", + "The phone is in emergency mode and you didn't provide an emergency programmer." + + " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + + " Keep the phone connected to the PC." + + " The unlock-sequence will resume automatically. To prevent this, provide an emergency programmer next time you will unlock a bootloader.", + null, false, WPinternalsStatus.WaitingForManualReset); + } + else + { + LogFile.Log("The phone is in emergency mode and we couldn't start the emergency programmer", LogType.ConsoleOnly); + LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", + "The phone is in emergency mode and we couldn't start the emergency programmer." + + " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + + " Keep the phone connected to the PC." + + " The unlock-sequence will resume automatically.", + null, false, WPinternalsStatus.WaitingForManualReset); + } + await Notifier.WaitForRemoval(); + + UpdateWorkingStatus("Initializing flash...", null, null); + + await Notifier.WaitForArrival(); + } + } + } + + #endregion + + if ((Notifier.CurrentInterface != PhoneInterfaces.Lumia_Flash) && (Notifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader)) + { + throw new WPinternalsException("Phone is in wrong mode", "The phone should have been detected in bootloader or flash mode. Instead it has been detected in " + Notifier.CurrentInterface.ToString() + " mode."); + } + + Model = (NokiaFlashModel)Notifier.CurrentModel; + UpdateWorkingStatus("Initializing flash...", null, null); + } + + try + { + // This will succeed on new models + Model.SwitchToFlashAppContext(); + Model.DisableRebootTimeOut(); + } + catch + { + // This will succeed on old models + Model.ResetPhoneToFlashMode(); + await Notifier.WaitForArrival(); + Model = (NokiaFlashModel)Notifier.CurrentModel; + } + + // The payloads must be ordered by the number of locations + // + // FlashApp processes payloads like this: + // - First payloads which are with one location, those can be sent in bulk + // - Then payloads with more than one location, those should not be sent in bulk + // + // If you do not order payloads like this, you will get an error, most likely hash mismatch + // + FlashingPayload[] payloads = Array.Empty(); + if (FlashParts != null) + { + payloads = GetNonOptimizedPayloads(FlashParts, FFU.ChunkSize, (uint)(Info.WriteBufferSize / FFU.ChunkSize), SetWorkingStatus, UpdateWorkingStatus).OrderBy(x => x.TargetLocations.Length).ToArray(); + } + + bool AssumeImageHeaderFallsInGap = true; + bool AllocateAsyncBuffersOnPhone = true; + bool AllocateBackupBuffersOnPhone = false; + UInt32 CurrentGapFill = EstimatedGapFill; + UInt32 OldGapFill; + bool Success = false; + bool Abort = false; + bool PhoneNeedsReset = false; + bool WaitForReset = false; + int AttemptCount = 0; + UInt32 ExploitHeaderAllocationSize = 0; + UInt32 LastHeaderV2Size = 0; + byte[] PartialHeader; + byte[] FfuHeader; + UInt64 CombinedFFUHeaderSize; + Allocation SecurityHeaderAllocation = null; + Allocation ImageHeaderAllocation = null; + Allocation StoreHeaderAllocation = null; + Allocation PartialHeaderAllocation = null; + UInt32 HeaderOffset = 0; + bool Scanning = false; + bool ResetScanning = false; + + Profile = App.Config.GetProfile(Info.PlatformID, Info.Firmware, FFU.GetFirmwareVersion()); + if (Profile == null) + { + LogFile.Log("No flashing profile found", LogType.FileAndConsole); + } + else + { + if (ShowProgress) + { + LogFile.Log("Flashing profile loaded", LogType.FileAndConsole); + } + + CurrentGapFill = Profile.FillSize; + ExploitHeaderAllocationSize = Profile.HeaderSize; + AllocateAsyncBuffersOnPhone = Profile.AllocateAsyncBuffersOnPhone; + AssumeImageHeaderFallsInGap = Profile.AssumeImageHeaderFallsInGap; + } + + do + { + AttemptCount++; + if ((Profile == null) || (AttemptCount > 1)) + { + LogFile.Log("Custom flash attempt: " + AttemptCount + " of " + MaximumAttempts, LogType.FileAndConsole); + + if (!Scanning) + { + SetWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)MaximumAttempts, Status: WPinternalsStatus.Scanning); + } + + Scanning = true; + UpdateWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)AttemptCount, Status: WPinternalsStatus.Scanning); + + ExploitHeaderAllocationSize = CurrentGapFill + (UInt32)FFU.ChunkSize; + } + + // Initialize flash attempts + // Make sure async buffers are allocated on the phone before overflow attempts, + // or else failed attempts may cause more memory-gaps and allocation becomes more unpredictable. + // The phone is rebooted after each attempt (to avoid memory-corruption). + // And it seems that that normally all allocations are in a big memory-gap, which was created before BootMgr was loaded. + // And there is still memory allocated in a lower range. + // And by allocating USB buffers, it could cause more memory-scattering. + // On Lumia 950 2 USB buffers are allocated and on Lumia 930 there is only one USB buffer allocated. + // StartAsyncFlash() is needed on 950 and 640. But not needed on 930! + // In any case, the allocation of the async-buffers should not be simulated in the Uefi Memory Simulator, + // because that would create a gap in the Simulator, instead of avoiding a gap on the phone. + // + if (AllocateAsyncBuffersOnPhone) + { + Model.StartAsyncFlash(); + Model.EndAsyncFlash(); // Ending Async flashing is not necessary for Lumia 950, but it is necessary for Lumia 640! + } + + if (AllocateBackupBuffersOnPhone) + { + Model.BackupPartitionToRam("MODEM_FSG"); + Model.BackupPartitionToRam("MODEM_FS1"); + Model.BackupPartitionToRam("MODEM_FS2"); + Model.BackupPartitionToRam("SSD"); + Model.BackupPartitionToRam("DPP"); + } + + HeaderOffset = 0; + SecurityHeaderAllocation = null; + ImageHeaderAllocation = null; + StoreHeaderAllocation = null; + PartialHeaderAllocation = null; + UInt32 DestinationChunkIndex = 0; + + // Create memory map + UefiMemorySim.Reset(); + SecurityHeaderAllocation = UefiMemorySim.AllocatePool((uint)FFU.SecurityHeader.Length); + SecurityHeaderAllocation.CopyToThisAllocation(FFU.SecurityHeader, 0, (uint)FFU.SecurityHeader.Length, 0); + if (!AssumeImageHeaderFallsInGap) + { + ImageHeaderAllocation = UefiMemorySim.AllocatePool((uint)FFU.ImageHeader.Length); + ImageHeaderAllocation.CopyToThisAllocation(FFU.ImageHeader, 0, (uint)FFU.ImageHeader.Length, 0); + } + StoreHeaderAllocation = UefiMemorySim.AllocatePool((uint)FFU.StoreHeader.Length); + StoreHeaderAllocation.CopyToThisAllocation(FFU.StoreHeader, 0, (uint)FFU.StoreHeader.Length, 0); + + // Simulate sending partial header + PartialHeaderAllocation = UefiMemorySim.AllocatePool(ExploitHeaderAllocationSize); + + CombinedFFUHeaderSize = FFU.HeaderSize; + FfuHeader = new byte[CombinedFFUHeaderSize]; + UInt32 TotalPayloadCount = (uint)payloads.Length; + bool HeadersFull; + int FlashingPhase = 0; + UInt32 FlashingPhaseStartPayloadIndex = 0; + UInt32 FlashingPhasePayloadCount = 0; + bool FlashInProgress = false; + byte[] Buffer = new byte[FFU.ChunkSize]; + LastHeaderV2Size = 0; + do + { + HeadersFull = false; + + // On every flashing phase we must fill the memory gap first, before sending the header. + if (CurrentGapFill > UefiMemorySim.PageSize) + { + if (FlashingPhase > 0) + { + // Avoid Error 0x0010 "Invalid sub block length". + // Headersize must increase compared to last time a header was sent, to avoid processing the header. + // Offset must be 0, to reset buffers. + // Previous data + new data must fit in new headersize. + // We can send an extra byte, because last memory buffer was sent including the tail. + // And there is always extra space in the memoryspace after the tail. + Model.SendFfuHeaderV2(LastHeaderV2Size + 1, 0, new byte[1], Options); + } + + // CurrentGapFill is the amount of data we want to be allocated on the phone + // But we send less data, so the header won't be processed yet. + PartialHeader = new byte[UefiMemorySim.PageSize]; + Model.SendFfuHeaderV2(CurrentGapFill, 0, PartialHeader, Options); // Fill memory gap -> This will fail on phones with Flash Protocol v1.x !! On Lumia 640 this will hang on receiving the response when EndAsyncFlash was not called. + } + + using (FileStream FfuFile = new(FFU.Path, FileMode.Open, FileAccess.Read)) + { + // On every flashing phase we need to send the full header again to reset all the counters. + FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); + Model.SendFfuHeaderV1(FfuHeader, Options); + + if (PerformFullFlashFirst && (FlashingPhase == 0)) + { + // If we flash the stock ROM at this point, the header is in memory is not overwritten yet. + // This means that after the last chunk was written, the flashing-status is written to NV (and also flushed). + // But this doesn't matter, because even when we want to overwrite NV, this happens later. + // When the header is successfully overwritten in memory, the next chunk of custom data will be allowed to be written. + + LogFile.Log("Starting custom flash attempt by doing a full flash.", LogType.FileAndConsole); + + UInt64 Position = CombinedFFUHeaderSize; + byte[] FlashPayload; + int ChunkIndex = 0; + UInt32 TotalChunkCount = (UInt32)FFU.TotalChunkCount; + + // Protocol v2 + FlashPayload = new byte[Info.WriteBufferSize]; + + while (Position < (UInt64)FfuFile.Length) + { + UInt32 CommonFlashPayloadSize = Info.WriteBufferSize; + if (((UInt64)FfuFile.Length - Position) < CommonFlashPayloadSize) + { + CommonFlashPayloadSize = (UInt32)((UInt64)FfuFile.Length - Position); + FlashPayload = new byte[CommonFlashPayloadSize]; + } + + FfuFile.Read(FlashPayload, 0, (int)CommonFlashPayloadSize); + ChunkIndex += (int)(CommonFlashPayloadSize / FFU.ChunkSize); + Model.SendFfuPayloadV2(FlashPayload, ShowProgress ? (int)((double)(ChunkIndex + 1) * 100 / TotalChunkCount) : 0, 0); + Position += CommonFlashPayloadSize; + } + } + } + + UInt32 NewWriteDescriptorOffset = 0xF8; + UInt32 CatalogSize = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.ContentStart + 0x18); + UInt32 NewHashOffset = 0x20 + CatalogSize; + + UInt32 WriteDescriptorLength = 0; + UInt32 WriteDescriptorCount = 0; + UInt32 HashTableSize = 0; + + if (PerformFullFlashFirst && (FlashingPhase == 0)) + { + // Set offset for new descriptor to end of descriptor-table + WriteDescriptorLength = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD4); + NewWriteDescriptorOffset += WriteDescriptorLength; + + // Get descriptor-count of the FFU + WriteDescriptorCount = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD0); + + // Set offset for new hash to end of hash-table + HashTableSize = ByteOperations.ReadUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.ContentStart + 0x1C); + NewHashOffset += HashTableSize; // Skip to the end of the original hash-table. + } + else + { + // From start of hash-table skip the first hashes for Image- and StoreHeaders. + HashTableSize = (UInt32)((FFU.ImageHeader.Length + FFU.StoreHeader.Length) / FFU.ChunkSize * 0x20); + NewHashOffset += HashTableSize; + } + + // Determine available space and number of payloads to send for this phase + UInt32 HashSpace = (UInt32)(FFU.SecurityHeader.Length - NewHashOffset); + UInt32 DescriptorSpace = (UInt32)(FFU.StoreHeader.Length - NewWriteDescriptorOffset); + + FlashingPhasePayloadCount = 0; + + // Always flash one extra chunk on the GPT (for purpose of testing and for making sure that first chunk does not contain all zero's). + UInt32 SecurityHeaderSize = FlashInProgress ? 0 : 0x20u; + UInt32 StoreHeaderSize = FlashInProgress ? 0 : 0x10u; + for (UInt32 i = FlashingPhaseStartPayloadIndex; i < payloads.Length; i++) + { + UInt32 NewSecurityHeaderSize = SecurityHeaderSize + payloads[i].GetSecurityHeaderSize(); + UInt32 NewStoreHeaderSize = StoreHeaderSize + payloads[i].GetStoreHeaderSize(); + + if (NewSecurityHeaderSize > HashSpace || NewStoreHeaderSize > DescriptorSpace) + { + HeadersFull = true; + break; + } + + FlashingPhasePayloadCount++; + SecurityHeaderSize = NewSecurityHeaderSize; + StoreHeaderSize = NewStoreHeaderSize; + } + + HashTableSize += SecurityHeaderSize; + WriteDescriptorCount += FlashingPhasePayloadCount + (FlashInProgress ? 0 : 1u); + WriteDescriptorLength += StoreHeaderSize; + + if (!ClearFlashingStatusAtEnd || HeadersFull) + { + WriteDescriptorCount++; + } + + // Write back new header values. + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD4, WriteDescriptorLength); + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xD0, WriteDescriptorCount); + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.ContentStart + 0x1C, HashTableSize); + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xEC, 0); // FlashOnlyTableLength - Make flash progress bar white immediately. + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + 0xE8, 1); // FlashOnlyTableCount + + // Write new descriptors + // First write descriptor and hash for the first GPT chunk + if (!FlashInProgress) // We only send the first GPT chunk when flash is not in progress yet. + { + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x00, 0x00000001); // Location count + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x04, 0x00000001); // Chunk count + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x08, 0x00000000); // Disk access method (0 = Begin, 2 = End) + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x0C, 0x00000000); // Chunk index = GPT + NewWriteDescriptorOffset += 0x10; + byte[] GPTHashValue = System.Security.Cryptography.SHA256.Create().ComputeHash(GPTChunk, 0, FFU.ChunkSize); // Hash is 0x20 bytes + System.Buffer.BlockCopy(GPTHashValue, 0, UefiMemorySim.Buffer, (int)(SecurityHeaderAllocation.ContentStart + NewHashOffset), 0x20); + NewHashOffset += 0x20; + } + + for (UInt32 i = 0; i < FlashingPhasePayloadCount; i++) + { + FlashingPayload payload = payloads[FlashingPhaseStartPayloadIndex + i]; + + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x00, (UInt32)payload.TargetLocations.Length); // Location count + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x04, payload.ChunkCount); // Chunk count + NewWriteDescriptorOffset += 0x08; + + foreach (UInt32 location in payload.TargetLocations) + { + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x00, 0x00000000); // Disk access method (0 = Begin, 2 = End) + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.ContentStart + NewWriteDescriptorOffset + 0x04, location); // Chunk index + NewWriteDescriptorOffset += 0x08; + } + + foreach (byte[] hashValue in payload.ChunkHashes) + { + // Write new hash + System.Buffer.BlockCopy(hashValue, 0, UefiMemorySim.Buffer, (Int32)(SecurityHeaderAllocation.ContentStart + NewHashOffset), 0x20); + NewHashOffset += 0x20; + } + } + + Stream CurrentStream = null; + int StreamIndex = 0; + int Step = 0; + try + { + // Send a small portion of header v2 at offset 0 + // The payload is smaller than the total headersize, so that it won't start processing the header now. + // This will allocate new memory at the bottom of the memory-pool, but it will not reset the previously imported ffu header. + Step = 1; + PartialHeader = new byte[UefiMemorySim.PageSize]; + Model.SendFfuHeaderV2(ExploitHeaderAllocationSize, 0, PartialHeader, Options); // SkipWrite = 1 (only works on engineering phones) + + // Now we will send the rest of the exploit header, but we will increase the total size even higher, so that it still won't start processing the headers. + // We've send only a small first part of the header. The allocated header was bigger: ExploitHeaderAllocationSize. + // But we HAVE to send the whole header. We can't skip a part. + Step = 2; + UInt32 ExploitHeaderRemaining = SecurityHeaderAllocation.TailEnd + 1 - PartialHeaderAllocation.ContentStart - (UInt32)PartialHeader.Length; + HeaderOffset = (UInt32)PartialHeader.Length; + while (ExploitHeaderRemaining > 0) + { + UInt32 CurrentFill = ExploitHeaderRemaining; + if (CurrentFill > Info.WriteBufferSize) + { + CurrentFill = Info.WriteBufferSize; + } + + PartialHeader = new byte[CurrentFill]; + PartialHeaderAllocation.CopyFromThisAllocation(HeaderOffset, CurrentFill, PartialHeader, 0); + Model.SendFfuHeaderV2(HeaderOffset + CurrentFill + 1, HeaderOffset, PartialHeader, Options); // Phone may crash here. USB write is done. USB read might fail due to crash. Happens on my own Lumia 650. + LastHeaderV2Size = HeaderOffset + CurrentFill + 1; + ExploitHeaderRemaining -= CurrentFill; + HeaderOffset += CurrentFill; + } + + // Send custom payload + Step = 3; + Int32 payloadCount = 0; + byte[] payloadBuffer = new byte[Info.WriteBufferSize]; + bool sendPayload = false; + for (Int32 i = FlashInProgress ? 0 : -1; i < FlashingPhasePayloadCount; i++) + { + string NewProgressText = "Flashing resources..."; + if (!FlashInProgress) + { + // First send the GPT chunk + Step = 4; + System.Buffer.BlockCopy(GPTChunk, 0, Buffer, 0, FFU.ChunkSize); + + Step = 8; + // This may fail. Normally with WPinternalsException for Invalid Hash or Data not aligned. + // Or it may fail with a BadConnectionException when the phone crashes and drops the connection. + Model.SendFfuPayloadV1(Buffer, 0); + if (!FlashInProgress) + { + Step = 9; + if (ShowProgress) + { + LogFile.Log("Flashing in progress!", LogType.FileAndConsole); + } + + FlashInProgress = true; + Scanning = false; + SetWorkingStatus(null, null, (UInt64?)payloads.Length, Status: WPinternalsStatus.Flashing); + } + } + else + { + Step = 5; + + FlashingPayload payload = payloads[FlashingPhaseStartPayloadIndex + i]; + + if (payloadCount == ((Info.WriteBufferSize / FFU.ChunkSize) - 1)) + { + sendPayload = true; + } + + if (FlashingPhaseStartPayloadIndex + i + 1 >= FlashingPhasePayloadCount) + { + sendPayload = true; + byte[] tmpBuffer = new byte[(payloadCount + 1) * FFU.ChunkSize]; + System.Buffer.BlockCopy(payloadBuffer, 0, tmpBuffer, 0, payloadCount * FFU.ChunkSize); + payloadBuffer = tmpBuffer; + } + + // Check if the next payload contains more than one chunk + if (!sendPayload && FlashingPhaseStartPayloadIndex + i + 1 < FlashingPhasePayloadCount && (payloads[FlashingPhaseStartPayloadIndex + i + 1].ChunkCount != 1 || payloads[FlashingPhaseStartPayloadIndex + i + 1].TargetLocations.Length != 1)) + { + sendPayload = true; + byte[] tmpBuffer = new byte[(payloadCount + 1) * FFU.ChunkSize]; + System.Buffer.BlockCopy(payloadBuffer, 0, tmpBuffer, 0, payloadCount * FFU.ChunkSize); + payloadBuffer = tmpBuffer; + } + + // We prepare the buffer setup above with all consecutive chunks we have to send in + // We can't send a single chunk otherwise we would get 0x1007: Payload data does not contain all data + if (payload.ChunkCount != 1) + { + NewProgressText = "Flashing common resources..."; + payloadBuffer = new byte[payload.ChunkCount * FFU.ChunkSize]; + for (uint j = 0; j < payload.ChunkCount; j++) + { + StreamIndex = (Int32)payload.StreamIndexes[j]; + FlashPart flashPart = FlashParts[StreamIndex]; + CurrentStream = flashPart.Stream; + CurrentStream.Seek(payload.StreamLocations[j], SeekOrigin.Begin); + + Step = 6; + Array.Clear(payloadBuffer, (Int32)(FFU.ChunkSize * j), FFU.ChunkSize); // Not really needed anymore? + + Step = 7; + CurrentStream.Read(payloadBuffer, (Int32)(FFU.ChunkSize * j), FFU.ChunkSize); + } + } + + if (payload.TargetLocations.Length != 1) + { + NewProgressText = "Flashing common resources..."; + payloadBuffer = new byte[FFU.ChunkSize]; + } + + if (payload.ChunkCount == 1) + { + StreamIndex = (Int32)payload.StreamIndexes[0]; + FlashPart flashPart = FlashParts[StreamIndex]; + CurrentStream = flashPart.Stream; + CurrentStream.Seek(payload.StreamLocations[0], SeekOrigin.Begin); + + if (payload.TargetLocations.Length == 1 && !string.IsNullOrEmpty(flashPart.ProgressText)) + { + NewProgressText = flashPart.ProgressText; + } + + CurrentStream.Read(payloadBuffer, FFU.ChunkSize * payloadCount, FFU.ChunkSize); + } + + Step = 8; + // This may fail. Normally with WPinternalsException for Invalid Hash or Data not aligned. + // Or it may fail with a BadConnectionException when the phone crashes and drops the connection. + + payloadCount++; + } + + UpdateWorkingStatus(NewProgressText, null, (UInt64?)(FlashingPhaseStartPayloadIndex + i + 1), WPinternalsStatus.Flashing); + + if (i != -1 && sendPayload) + { + // This fails when sending multiple chunks per payload with 0x1003: Hash mismatch + Model.SendFfuPayloadV2(payloadBuffer, ShowProgress ? (Int32)((FlashingPhaseStartPayloadIndex + i + 1) * 100 / payloads.Length) : 0); + sendPayload = false; + payloadCount = 0; + payloadBuffer = new byte[Info.WriteBufferSize]; + } + + DestinationChunkIndex++; + } + + Step = 10; + FlashingPhaseStartPayloadIndex += FlashingPhasePayloadCount; + + Step = 11; + if (!HeadersFull) + { + Step = 12; + App.Config.SetProfile(Info.Type, Info.PlatformID, Info.ProductCode, Info.Firmware, FFU.GetFirmwareVersion(), CurrentGapFill, ExploitHeaderAllocationSize, AssumeImageHeaderFallsInGap, AllocateAsyncBuffersOnPhone); + if (ShowProgress) + { + LogFile.Log("Custom flash succeeded!", LogType.FileAndConsole); + } + + Success = true; + } + else + { + // At this point we're missing a few payloads, so we need to start again. + LogFile.Log("Reinitilizing a new flashing attempt because headers were full and we're not quite done yet!"); + } + } + catch (BadConnectionException) + { + LogFile.Log("Connection to phone is lost - " + + Step.ToString() + " " + + StreamIndex.ToString() + " " + + (CurrentStream == null ? "0" : CurrentStream.Position.ToString()) + " " + + FlashingPhase.ToString() + " " + + FlashingPhaseStartPayloadIndex.ToString() + " " + + DestinationChunkIndex.ToString()); + LogFile.Log("Expect phone to reboot", LogType.FileAndConsole); + WaitForReset = true; + } + catch (Exception Ex) + { + if (FlashInProgress) + { + // Normally, when we end up here, we were not in process of flashing yet. + // It would be a flash attempt which failed. + // But if we were already flashing, then something else is wrong. + // We need more info and stop flashing. + LogFile.Log("Custom flash failed", LogType.FileAndConsole); + LogFile.LogException(Ex, LogType.FileOnly, + Step.ToString() + " " + + StreamIndex.ToString() + " " + + (CurrentStream == null ? "0" : CurrentStream.Position.ToString()) + " " + + FlashingPhase.ToString() + " " + + FlashingPhaseStartPayloadIndex.ToString() + " " + + DestinationChunkIndex.ToString()); + Abort = true; + } + else + { + LogFile.Log("Custom flash attempt failed", LogType.FileAndConsole); + LogFile.LogException(Ex, LogType.FileOnly, + Step.ToString() + " " + + StreamIndex.ToString() + " " + + (CurrentStream == null ? "0" : CurrentStream.Position.ToString()) + " " + + FlashingPhase.ToString() + " " + + FlashingPhaseStartPayloadIndex.ToString() + " " + + DestinationChunkIndex.ToString()); + } + + PhoneNeedsReset = true; + } + + if (FlashInProgress) + { + FlashingPhase++; + } + } + while (HeadersFull && FlashInProgress && !Abort); + + if (!Success) + { + if ((Profile != null) && !Abort) + { + LogFile.Log("Flashing profile was loaded, but it is not working", LogType.FileAndConsole); + LogFile.Log("Attempting to find a working profile", LogType.FileAndConsole); + ResetScanning = true; + } + + if (PhoneNeedsReset) + { + Model.ResetPhone(); + WaitForReset = true; + } + + if (WaitForReset) + { + #region Properly recover from reset between flash attempts - many phones respond differently + + Timeout = false; + try + { + await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); + } + catch (TimeoutException) + { + Timeout = true; + } + if ((Notifier.CurrentInterface == null) && (!AutoEmergencyReset || Timeout)) + { + AutoEmergencyReset = false; + + if (Timeout) + { + LogFile.Log("The phone is not responding", LogType.ConsoleOnly); + LogFile.Log("It might be in emergency mode, while you have no matching driver installed", LogType.ConsoleOnly); + } + LogFile.Log("To continue the unlock-sequence, the phone needs to be rebooted", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + UpdateWorkingStatus("You need to manually reset your phone now!", (Timeout ? "The phone is not responding. It might be in emergency mode, while you have no matching driver installed. " : "") + "To continue the unlock-sequence, the phone needs to be rebooted. Keep the phone connected to the PC. The unlock-sequence will resume automatically.", null, WPinternalsStatus.WaitingForManualReset); + + await Notifier.WaitForRemoval(); + + UpdateWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)AttemptCount, Status: WPinternalsStatus.Scanning); + + await Notifier.WaitForArrival(); + } + if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) + { + bool FailedToStartProgrammer = false; + if (ProgrammerPath != null) + { + QualcommSahara Sahara = new((QualcommSerial)Notifier.CurrentModel); + try + { + await Sahara.Reset(ProgrammerPath); + await Notifier.WaitForArrival(); + } + catch (BadConnectionException) + { + FailedToStartProgrammer = true; + } + } + + if (ProgrammerPath == null || FailedToStartProgrammer) + { + ((QualcommSerial)Notifier.CurrentModel).Close(); // Prevent "Resource in use"; + + Timeout = false; + if (AutoEmergencyReset) + { + try + { + await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); + } + catch (TimeoutException) + { + Timeout = true; + } + catch (Exception ex) + { + LogFile.Log("An unexpected error happened", LogType.FileAndConsole); + LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); + FailedToStartProgrammer = true; + } + } + if (!AutoEmergencyReset || Timeout) + { + AutoEmergencyReset = false; + if (!FailedToStartProgrammer) + { + LogFile.Log("The phone is in emergency mode and you didn't provide an emergency programmer", LogType.ConsoleOnly); + LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("To prevent this, provide an emergency programmer next time you will unlock a bootloader", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", + "The phone is in emergency mode and you didn't provide an emergency programmer." + + " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + + " Keep the phone connected to the PC." + + " The unlock-sequence will resume automatically. To prevent this, provide an emergency programmer next time you will unlock a bootloader.", + null, false, WPinternalsStatus.WaitingForManualReset); + } + else + { + LogFile.Log("The phone is in emergency mode and we couldn't start the emergency programmer", LogType.ConsoleOnly); + LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", + "The phone is in emergency mode and we couldn't start the emergency programmer." + + " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + + " Keep the phone connected to the PC." + + " The unlock-sequence will resume automatically.", + null, false, WPinternalsStatus.WaitingForManualReset); + } + + await Notifier.WaitForRemoval(); + + UpdateWorkingStatus("Scanning for flashing-profile - attempt " + AttemptCount.ToString() + " of " + MaximumAttempts.ToString(), "Your phone may appear to be in a reboot-loop. This is expected behavior. Don't interfere this process.", (uint)AttemptCount, Status: WPinternalsStatus.Scanning); + + await Notifier.WaitForArrival(); + } + } + } + + #endregion + + // Sanity check: must be in flash mode + if ((Notifier.CurrentInterface != PhoneInterfaces.Lumia_Flash) && (Notifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader)) + { + break; + } + + Model = (NokiaFlashModel)Notifier.CurrentModel; + + // In case we are on an Engineering phone which isn't stuck in flashmode and booted to BootMgrApp + Model.SwitchToFlashAppContext(); + Model.DisableRebootTimeOut(); + } + + PhoneNeedsReset = false; + WaitForReset = false; + + // Calculate variables for next attempt + if (ResetScanning) + { + AssumeImageHeaderFallsInGap = true; + AllocateAsyncBuffersOnPhone = true; + AllocateBackupBuffersOnPhone = false; + CurrentGapFill = EstimatedGapFill; + Profile = null; + ResetScanning = false; + } + else if (Experimental && !AllocateBackupBuffersOnPhone) + { + AllocateBackupBuffersOnPhone = true; + } + else + { + AllocateBackupBuffersOnPhone = false; + if (AllocateAsyncBuffersOnPhone) + { + AllocateAsyncBuffersOnPhone = false; + } + else + { + AllocateAsyncBuffersOnPhone = true; + OldGapFill = CurrentGapFill; + if (OldGapFill <= EstimatedGapFill) + { + CurrentGapFill = EstimatedGapFill + (EstimatedGapFill - OldGapFill) + (UInt32)FFU.ChunkSize; + if (CurrentGapFill > MaximumGapFill) + { + if (OldGapFill > 0) + { + CurrentGapFill = OldGapFill - (UInt32)FFU.ChunkSize; + } + else if (AssumeImageHeaderFallsInGap) + { + AssumeImageHeaderFallsInGap = false; + CurrentGapFill = EstimatedGapFill; + } + else + { + break; + } + } + } + else + { + if (OldGapFill <= (EstimatedGapFill * 2)) + { + CurrentGapFill = EstimatedGapFill - (OldGapFill - EstimatedGapFill); + } + else + { + CurrentGapFill = OldGapFill + (UInt32)FFU.ChunkSize; + if (CurrentGapFill > MaximumGapFill) + { + if (AssumeImageHeaderFallsInGap) + { + AssumeImageHeaderFallsInGap = false; + CurrentGapFill = EstimatedGapFill; + } + else + { + break; + } + } + } + } + } + } + } + } + while (!Success && !Abort); + + // Now we will first try to create a memory corruption in the phone, before we reset the phone. + // The memory corruption will cause the phone to abort the shutdown-sequence and switch to emergency mode immediately. + // We already avoided that the FlashingStatus was written to NV. + // But we also need to avoid that the BootFlag is written to NV when the phone is properly shut down, because that will overwrite the NV vars we wrote earlier. + if (Success && !ClearFlashingStatusAtEnd) + { + // Make the phone crash here! + // This will actually make the phone crash when it frees memory during shutdown or reboot of the phone + + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, SecurityHeaderAllocation.HeadStart + 4, 0); // Set allocation size to 0 in allocationhead + ByteOperations.WriteUInt32(UefiMemorySim.Buffer, StoreHeaderAllocation.HeadStart + 4, 0); // Set allocation size to 0 in allocationhead + if (CurrentGapFill > UefiMemorySim.PageSize) + { + Model.SendFfuHeaderV2(LastHeaderV2Size + 1, 0, new byte[1], Options); + PartialHeader = new byte[UefiMemorySim.PageSize]; + Model.SendFfuHeaderV2(CurrentGapFill, 0, PartialHeader, Options); // Fill memory gap + } + using (FileStream FfuFile = new(FFU.Path, FileMode.Open, FileAccess.Read)) + { + // On every flashing phase we need to send the full header again, because this triggers ffu_import_invalidate(), which is necessary to reset all the counters. + FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); + Model.SendFfuHeaderV1(FfuHeader, Options); + } + PartialHeader = new byte[UefiMemorySim.PageSize]; + Model.SendFfuHeaderV2(ExploitHeaderAllocationSize, 0, PartialHeader, Options); // SkipWrite = 1 (only works on engineering phones) + UInt32 ExploitHeaderRemaining = SecurityHeaderAllocation.TailEnd + 1 - PartialHeaderAllocation.ContentStart - (UInt32)PartialHeader.Length; + HeaderOffset = (UInt32)PartialHeader.Length; + while (ExploitHeaderRemaining > 0) + { + UInt32 CurrentFill = ExploitHeaderRemaining; + if (CurrentFill > Info.WriteBufferSize) + { + CurrentFill = Info.WriteBufferSize; + } + + PartialHeader = new byte[CurrentFill]; + PartialHeaderAllocation.CopyFromThisAllocation(HeaderOffset, CurrentFill, PartialHeader, 0); + Model.SendFfuHeaderV2(HeaderOffset + CurrentFill + 1, HeaderOffset, PartialHeader, Options); + LastHeaderV2Size = HeaderOffset + CurrentFill + 1; + ExploitHeaderRemaining -= CurrentFill; + HeaderOffset += CurrentFill; + } + + // Do the actual reset, which will result in a crash while cleaning up memory + ((NokiaFlashModel)Notifier.CurrentModel).ResetPhone(); + + LogFile.Log("Phone performs hard exit", LogType.FileAndConsole); + + #region Properly recover from reset at the end of custom flash - many phones respond differently + + // Wait for the phone to boot to emergency mode + // Emergency mode is always triggered on purpose to avoid writing NV vars + // We also wait for emergency mode when no valid programmer is present, or else caller-code will not know at which stage the phone is rebooting + + // Possibilities here: + // - Lumia 950 or 950 XL which does not crash to emergency mode, switching to mass storage mode, but not assigning a drive-letter -> Bootmgr, Nothing + // - Lumia 950 or 950 XL which does not crash to emergency mode, switching to mass storage mode, and assigning a drive-letter -> Bootmgr, MSM + // - Other Lumia's, switching to mass storage mode, but not assigning a drive-letter -> Bootmgr, Nothing + + Timeout = false; + try + { + await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); + } + catch (TimeoutException) + { + Timeout = true; + } + if ((Notifier.CurrentInterface == null) && (!AutoEmergencyReset || Timeout)) + { + AutoEmergencyReset = false; + + if (Timeout) + { + LogFile.Log("The phone is not responding", LogType.ConsoleOnly); + LogFile.Log("It might be in emergency mode, while you have no matching driver installed", LogType.ConsoleOnly); + } + LogFile.Log("To continue the unlock-sequence, the phone needs to be rebooted", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", (Timeout ? "The phone is not responding. It might be in emergency mode, while you have no matching driver installed. " : "") + "To continue the unlock-sequence, the phone needs to be rebooted. Keep the phone connected to the PC. The unlock-sequence will resume automatically.", null, false, WPinternalsStatus.WaitingForManualReset); + + await Notifier.WaitForRemoval(); + + SetWorkingStatus("Rebooting phone..."); + } + if (Notifier.CurrentInterface == PhoneInterfaces.Qualcomm_Download) + { + bool FailedToStartProgrammer = false; + if (ProgrammerPath != null) + { + QualcommSahara Sahara = new((QualcommSerial)Notifier.CurrentModel); + try + { + await Sahara.Reset(ProgrammerPath); + await Notifier.WaitForArrival(); + } + catch (BadConnectionException) + { + FailedToStartProgrammer = true; + } + } + + if (ProgrammerPath == null || FailedToStartProgrammer) + { + ((QualcommSerial)Notifier.CurrentModel).Close(); // Prevent "Resource in use"; + + Timeout = false; + if (AutoEmergencyReset) + { + try + { + await Notifier.WaitForArrival().TimeoutAfter(TimeSpan.FromSeconds(40)); + } + catch (TimeoutException) + { + Timeout = true; + } + catch (Exception ex) + { + LogFile.Log("An unexpected error happened", LogType.FileAndConsole); + LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole); + FailedToStartProgrammer = true; + } + } + if (!AutoEmergencyReset || Timeout) + { + AutoEmergencyReset = false; + + if (!FailedToStartProgrammer) + { + LogFile.Log("The phone is in emergency mode and you didn't provide an emergency programmer", LogType.ConsoleOnly); + LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("To prevent this, provide an emergency programmer next time you will unlock a bootloader", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", + "The phone is in emergency mode and you didn't provide an emergency programmer." + + " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + + " Keep the phone connected to the PC." + + " The unlock-sequence will resume automatically. To prevent this, provide an emergency programmer next time you will unlock a bootloader.", + null, false, WPinternalsStatus.WaitingForManualReset); + } + else + { + LogFile.Log("The phone is in emergency mode and we couldn't start the emergency programmer", LogType.ConsoleOnly); + LogFile.Log("This phone also doesn't seem to reboot after a timeout, so you got to help a bit", LogType.ConsoleOnly); + LogFile.Log("Keep the phone connected to the PC", LogType.ConsoleOnly); + LogFile.Log("Reboot the phone manually by pressing and holding the power-button of the phone for about 10 seconds until it vibrates", LogType.ConsoleOnly); + LogFile.Log("The unlock-sequence will resume automatically", LogType.ConsoleOnly); + LogFile.Log("Waiting for manual reset of the phone...", LogType.ConsoleOnly); + + SetWorkingStatus("You need to manually reset your phone now!", + "The phone is in emergency mode and we couldn't start the emergency programmer." + + " This phone also doesn't seem to reboot after a timeout, so you got to help a bit." + + " Keep the phone connected to the PC." + + " The unlock-sequence will resume automatically.", + null, false, WPinternalsStatus.WaitingForManualReset); + } + + await Notifier.WaitForRemoval(); + + SetWorkingStatus("Rebooting phone..."); + + // await Notifier.WaitForArrival(); // Function will exit while phone is rebooting + } + } + } + + #endregion + } + else + { + // If we didn't do a hard exit, we need to do a normal reboot + ((NokiaFlashModel)Notifier.CurrentModel).ResetPhone(); + } + + if (Success) + { + ExitSuccess("Flash succeeded!", null); + } + else + { + throw new WPinternalsException("Custom flash failed"); + } + } + + internal class FlashingPayload + { + public UInt32 ChunkCount; + public byte[][] ChunkHashes; + public UInt32[] TargetLocations; + public UInt32[] StreamIndexes; + public Int64[] StreamLocations; + + public FlashingPayload(UInt32 ChunkCount, byte[][] ChunkHashes, UInt32[] TargetLocations, UInt32[] StreamIndexes, Int64[] StreamLocations) + { + this.ChunkCount = ChunkCount; + this.ChunkHashes = ChunkHashes; + this.TargetLocations = TargetLocations; + this.StreamIndexes = StreamIndexes; + this.StreamLocations = StreamLocations; + } + + public UInt32 GetSecurityHeaderSize() + { + return 0x20 * (UInt32)ChunkHashes.Length; + } + + public UInt32 GetStoreHeaderSize() + { + return 0x08 * ((UInt32)TargetLocations.Length + 1); + } + } + + // + // Function to fall back into the legacy implementation of custom flash, to test the modifications done in the custom flash function + // in LumiaV2UnlockBootViewModel + // + internal static FlashingPayload[] GetNonOptimizedPayloads(List flashParts, Int32 chunkSize, UInt32 MaximumChunkCount, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null) + { + long TotalProcess1 = 0; + for (Int32 j = 0; j < flashParts.Count; j++) + { + FlashPart flashPart = flashParts[j]; + TotalProcess1 += flashPart.Stream.Length / chunkSize; + } + + ulong CurrentProcess1 = 0; + SetWorkingStatus("Hashing resources...", "Initializing flash...", (UInt64)TotalProcess1, Status: WPinternalsStatus.Initializing); + + var crypto = System.Security.Cryptography.SHA256.Create(); + List flashingPayloads = new(); + if (flashParts == null) + { + return flashingPayloads.ToArray(); + } + + for (UInt32 j = 0; j < flashParts.Count; j++) + { + FlashPart flashPart = flashParts[(Int32)j]; + flashPart.Stream.Seek(0, SeekOrigin.Begin); + var totalChunkCount = flashPart.Stream.Length / chunkSize; + for (UInt32 i = 0; i < totalChunkCount; i++) + { + UpdateWorkingStatus("Hashing resources...", "Initializing flash...", CurrentProcess1, WPinternalsStatus.Initializing); + byte[] buffer = new byte[chunkSize]; + Int64 position = flashPart.Stream.Position; + flashPart.Stream.Read(buffer, 0, chunkSize); + flashingPayloads.Add(new FlashingPayload(1, new byte[][] { crypto.ComputeHash(buffer) }, new UInt32[] { (flashPart.StartSector * 0x200 / (UInt32)chunkSize) + i }, new UInt32[] { j }, new Int64[] { position })); + CurrentProcess1++; + } + } + + return flashingPayloads.ToArray(); + } + + // + // This function finds in an optimized way the number of duplicate chunks in a given stream, and returns + // a list of elements, defining a chunk occurence in said stream and the chunk precomputed SHA256 hash. + // + internal static FlashingPayload[] GetOptimizedPayloads(List flashParts, Int32 chunkSize, UInt32 MaximumChunkCount, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null) + { + List flashingPayloads = new(); + if (flashParts == null) + { + return flashingPayloads.ToArray(); + } + + long TotalProcess1 = 0; + for (Int32 j = 0; j < flashParts.Count; j++) + { + FlashPart flashPart = flashParts[j]; + TotalProcess1 += flashPart.Stream.Length / chunkSize; + } + + ulong CurrentProcess1 = 0; + SetWorkingStatus("Hashing resources...", "Initializing flash...", (UInt64)TotalProcess1, Status: WPinternalsStatus.Initializing); + + using (System.Security.Cryptography.SHA256 crypto = System.Security.Cryptography.SHA256.Create()) + { + for (UInt32 j = 0; j < flashParts.Count; j++) + { + FlashPart flashPart = flashParts[(Int32)j]; + flashPart.Stream.Seek(0, SeekOrigin.Begin); + var totalChunkCount = flashPart.Stream.Length / chunkSize; + for (UInt32 i = 0; i < totalChunkCount; i++) + { + UpdateWorkingStatus("Hashing resources...", "Initializing flash...", CurrentProcess1, WPinternalsStatus.Initializing); + byte[] buffer = new byte[chunkSize]; + Int64 position = flashPart.Stream.Position; + flashPart.Stream.Read(buffer, 0, chunkSize); + var hash = crypto.ComputeHash(buffer); + + if (flashingPayloads.Any(x => ByteOperations.Compare(x.ChunkHashes[0], hash))) + { + var payloadIndex = flashingPayloads.FindIndex(x => ByteOperations.Compare(x.ChunkHashes[0], hash)); + var locationList = flashingPayloads[payloadIndex].TargetLocations.ToList(); + locationList.Add((flashPart.StartSector * 0x200 / (UInt32)chunkSize) + i); + flashingPayloads[payloadIndex].TargetLocations = locationList.ToArray(); + } + else + { + flashingPayloads.Add(new FlashingPayload(1, new byte[][] { hash }, new UInt32[] { (flashPart.StartSector * 0x200 / (UInt32)chunkSize) + i }, new UInt32[] { j }, new Int64[] { position })); + } + + CurrentProcess1++; + } + } + } + + return flashingPayloads.ToArray(); + } + + internal static string GetProgrammerPath(byte[] RKH, string Type) + { + IEnumerable RKHEntries = App.Config.EmergencyRepository.Where(e => StructuralComparisons.StructuralEqualityComparer.Equals(e.RKH, RKH) && e.ProgrammerExists()); + if (RKHEntries.Any()) + { + if (RKHEntries.Count() == 1) + { + return RKHEntries.First().ProgrammerPath; + } + else + { + EmergencyFileEntry RKHEntry = RKHEntries.FirstOrDefault(e => string.Equals(e.Type, Type, StringComparison.CurrentCulture)); + if (RKHEntry != null) + { + return RKHEntry.ProgrammerPath; + } + else + { + return RKHEntries.First().ProgrammerPath; // Cannot be sure this is the right one!! + } + } + } + else + { + EmergencyFileEntry TypeEntry = App.Config.EmergencyRepository.Find(e => string.Equals(e.Type, Type, StringComparison.CurrentCulture) && e.ProgrammerExists()); + if (TypeEntry != null) + { + return TypeEntry.ProgrammerPath; + } + else + { + return null; + } + } + } + + // Assumes phone with Flash protocol v2 + // Assumes phone is in flash mode + internal async static Task LumiaV2FlashArchive(PhoneNotifierViewModel Notifier, string ArchivePath, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) + { + LogFile.BeginAction("FlashCustomROM"); + + NokiaFlashModel FlashModel = (NokiaFlashModel)Notifier.CurrentModel; + + // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. + // We need the fist sector if we want to write back the GPT. + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); + GPT GPT = new(GPTChunk); + + Partition Target; + FlashPart Part; + List Parts = new(); + ulong MainOSOldSectorCount = 0; + ulong MainOSNewSectorCount = 0; + ulong DataOldSectorCount = 0; + ulong DataNewSectorCount = 0; + ulong FirstMainOSSector = 0; + int PartitionCount = 0; + ulong TotalSizeSectors = 0; + bool IsUnlocked = false; + bool GPTChanged = false; + + bool ClearFlashingStatus = true; + + if (SetWorkingStatus == null) + { + SetWorkingStatus = (m, s, v, a, st) => { }; + } + + if (UpdateWorkingStatus == null) + { + UpdateWorkingStatus = (m, s, v, st) => { }; + } + + if (ExitSuccess == null) + { + ExitSuccess = (m, s) => { }; + } + + if (ExitSuccess == null) + { + ExitSuccess = (m, s) => { }; + } + + SetWorkingStatus("Initializing flash...", null, null, Status: WPinternalsStatus.Initializing); + + try + { + using FileStream FileStream = new(ArchivePath, FileMode.Open); + using ZipArchive Archive = new(FileStream, ZipArchiveMode.Read); + // Determine if there is a partition layout present + ZipArchiveEntry PartitionEntry = Archive.GetEntry("Partitions.xml"); + if (PartitionEntry == null) + { + GPT.MergePartitions(null, true, Archive); + GPTChanged |= GPT.HasChanged; + } + else + { + using Stream ZipStream = PartitionEntry.Open(); + using StreamReader ZipReader = new(ZipStream); + string PartitionXml = ZipReader.ReadToEnd(); + GPT.MergePartitions(PartitionXml, true, Archive); + GPTChanged |= GPT.HasChanged; + } + + // First determine if we need a new GPT! + foreach (ZipArchiveEntry Entry in Archive.Entries) + { + if (!Entry.FullName.Contains("/")) // No subfolders + { + string PartitionName = Entry.Name; + int Pos = PartitionName.IndexOf('.'); + if (Pos >= 0) + { + PartitionName = PartitionName.Substring(0, Pos); + } + + Partition Partition = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); + if (Partition != null) + { + using DecompressedStream DecompressedStream = new(Entry.Open()); + ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200; + try + { + StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200; + } + catch { } + + TotalSizeSectors += StreamLengthInSectors; + PartitionCount++; + + if (string.Equals(PartitionName, "MainOS", StringComparison.CurrentCultureIgnoreCase)) + { + MainOSOldSectorCount = Partition.SizeInSectors; + MainOSNewSectorCount = StreamLengthInSectors; + FirstMainOSSector = Partition.FirstSector; + } + else if (string.Equals(PartitionName, "Data", StringComparison.CurrentCultureIgnoreCase)) + { + DataOldSectorCount = Partition.SizeInSectors; + DataNewSectorCount = StreamLengthInSectors; + } + else if (StreamLengthInSectors > Partition.SizeInSectors) + { + LogFile.Log("Flash failed! Size of partition '" + PartitionName + "' is too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Size of partition '" + PartitionName + "' is too big."); + return; + } + else if (string.Equals(PartitionName, "EFIESP", StringComparison.CurrentCultureIgnoreCase)) + { + ulong EfiespLength = StreamLengthInSectors * 0x200; + byte[] EfiespBinary = new byte[EfiespLength]; + DecompressedStream.Read(EfiespBinary, 0, (int)EfiespLength); + IsUnlocked = (ByteOperations.ReadUInt32(EfiespBinary, 0x20) == (EfiespBinary.Length / 0x200 / 2)) && ByteOperations.ReadAsciiString(EfiespBinary, (UInt32)(EfiespBinary.Length / 2) + 3, 8) == "MSDOS5.0"; + if (IsUnlocked) + { + Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); + if (IsUnlockedFlag == null) + { + IsUnlockedFlag = new Partition + { + Name = "IS_UNLOCKED", + Attributes = 0, + PartitionGuid = Guid.NewGuid(), + PartitionTypeGuid = Guid.NewGuid(), + FirstSector = 0x40, + LastSector = 0x40 + }; + GPT.Partitions.Add(IsUnlockedFlag); + GPTChanged = true; + ClearFlashingStatus = false; + } + } + else + { + Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); + if (IsUnlockedFlag != null) + { + GPT.Partitions.Remove(IsUnlockedFlag); + GPTChanged = true; + ClearFlashingStatus = false; + } + } + } + } + } + } + + if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) + { + if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) + { + UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; + if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) + { + // MainOS and Data partitions need to be re-aligned! + Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); + Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); + MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; + DataPartition.FirstSector = MainOSPartition.LastSector + 1; + if ((DataPartition.FirstSector % 0x100) > 0) + { + DataPartition.FirstSector = (DataPartition.FirstSector + 0x100) / 0x100 * 0x100; + } + + DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; + + GPTChanged = true; + } + else + { + LogFile.Log("Flash failed! Sizes of partitions 'MainOS' and 'Data' together are too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); + return; + } + } + } + else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'MainOS' is too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); + return; + } + else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'Data' is too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Size of partition 'Data' is too big."); + return; + } + + if (!ClearFlashingStatus) + { + if (!IsUnlocked) + { + // Undo secure boot exploit + Partition NvBackupPartition = GPT.GetPartition("BACKUP_BS_NV"); + if (NvBackupPartition != null) + { + // This must be a left over of a half unlocked bootloader + Partition NvPartition = GPT.GetPartition("UEFI_BS_NV"); + NvBackupPartition.Name = "UEFI_BS_NV"; + NvBackupPartition.PartitionGuid = NvPartition.PartitionGuid; + NvBackupPartition.PartitionTypeGuid = NvPartition.PartitionTypeGuid; + GPT.Partitions.Remove(NvPartition); + GPTChanged = true; + } + + PhoneInfo Info = FlashModel.ReadPhoneInfo(false); + + // We should only clear NV if there was no backup NV to be restored and the current NV contains the SB unlock. + if ((NvBackupPartition == null) && !Info.UefiSecureBootEnabled) + { + // ClearNV + Part = new FlashPart(); + Partition Target2 = GPT.GetPartition("UEFI_BS_NV"); + Part.StartSector = (UInt32)Target2.FirstSector; + Part.Stream = new MemoryStream(new byte[0x40000]); + Parts.Add(Part); + } + } + else + { + // Now add NV partition + Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); + Partition UEFI_BS_NV; + if (BACKUP_BS_NV == null) + { + BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); + Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; + Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; + BACKUP_BS_NV.Name = "BACKUP_BS_NV"; + BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); + BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); + UEFI_BS_NV = new Partition + { + Name = "UEFI_BS_NV", + Attributes = BACKUP_BS_NV.Attributes, + PartitionGuid = OriginalPartitionGuid, + PartitionTypeGuid = OriginalPartitionTypeGuid, + FirstSector = BACKUP_BS_NV.LastSector + 1 + }; + UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; + GPT.Partitions.Add(UEFI_BS_NV); + GPTChanged = true; + } + + Part = new FlashPart(); + Target = GPT.GetPartition("UEFI_BS_NV"); + Part.StartSector = (UInt32)Target.FirstSector; // GPT is prepared for 64-bit sector-offset, but flash app isn't. + Part.Stream = new SeekableStream(() => + { + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + + // Magic! + // The SB resource is a compressed version of a raw NV-variable-partition. + // In this partition the SecureBoot variable is disabled. + // It overwrites the variable in a different NV-partition than where this variable is stored usually. + // This normally leads to endless-loops when the NV-variables are enumerated. + // But the partition contains an extra hack to break out the endless loops. + var stream = assembly.GetManifestResourceStream("WPinternals.SB"); + + return new DecompressedStream(stream); + }); + Parts.Add(Part); + } + } + + if (GPTChanged) + { + GPT.Rebuild(); + Part = new FlashPart + { + StartSector = 0, + Stream = new MemoryStream(GPTChunk) + }; + Parts.Add(Part); + } + + // And then add the partitions from the archive + if (PartitionCount > 0) + { + foreach (ZipArchiveEntry Entry in Archive.Entries) + { + if (!Entry.FullName.Contains("/")) // No subfolders + { + // "MainOS.bin.gz" => "MainOS" + string PartitionName = Entry.Name; + int Pos = PartitionName.IndexOf('.'); + if (Pos >= 0) + { + PartitionName = PartitionName.Substring(0, Pos); + } + + Target = GPT.Partitions.Find(p => string.Equals(p.Name, PartitionName, StringComparison.CurrentCultureIgnoreCase)); + if (Target != null) + { + Part = new FlashPart + { + StartSector = (UInt32)Target.FirstSector, + Stream = new SeekableStream(() => new DecompressedStream(Entry.Open()), Entry.Length), + ProgressText = "Flashing partition " + Target.Name + }; + Parts.Add(Part); + LogFile.Log("Partition name=" + PartitionName + ", startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Entry.Length / 0x200).ToString("X8"), LogType.FileOnly); + } + } + } + + Parts = Parts.OrderBy(p => p.StartSector).ToList(); + int Count = 1; + Parts.Where(p => p.ProgressText?.StartsWith("Flashing partition ") == true).ToList().ForEach((p) => + { + p.ProgressText += " (" + Count.ToString() + "/" + PartitionCount.ToString() + ")"; + Count++; + }); + + // Do actual flashing! + await LumiaV2CustomFlash(Notifier, null, false, false, Parts, true, ClearFlashingStatus, false, true, false, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure); + } + else + { + LogFile.Log("Flash failed! No valid partitions found in the archive.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "No valid partitions found in the archive"); + return; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + ExitFailure(Ex.Message, Ex is WPinternalsException ? ((WPinternalsException)Ex).SubMessage : null); + } + + LogFile.EndAction("FlashCustomROM"); + } + + internal async static Task LumiaV2FixBoot(PhoneNotifierViewModel Notifier, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) + { + LogFile.BeginAction("FixBoot"); + + LogFile.Log("Command: Fix boot after unlocking bootloader", LogType.FileAndConsole); + NokiaFlashModel FlashModel = (NokiaFlashModel)Notifier.CurrentModel; + + if (SetWorkingStatus == null) + { + SetWorkingStatus = (m, s, v, a, st) => { }; + } + + if (UpdateWorkingStatus == null) + { + UpdateWorkingStatus = (m, s, v, st) => { }; + } + + if (ExitSuccess == null) + { + ExitSuccess = (m, s) => { }; + } + + if (ExitFailure == null) + { + ExitFailure = (m, s) => { }; + } + + try + { + await SwitchModeViewModel.SwitchToWithProgress(Notifier, PhoneInterfaces.Lumia_MassStorage, (msg, sub) => SetWorkingStatus(msg, sub, null, Status: WPinternalsStatus.SwitchingMode)); + SetWorkingStatus("Applying patches...", null, null, Status: WPinternalsStatus.Patching); + App.PatchEngine.TargetPath = ((MassStorage)Notifier.CurrentModel).Drive + "\\"; + bool PatchResult = App.PatchEngine.Patch("SecureBootHack-MainOS"); + if (!PatchResult) + { + throw new WPinternalsException("Patch failed", "An error occured while patching Operating System files on the MainOS partition of your phone. Make sure your phone runs a supported Operating System version."); + } + + LogFile.Log("Fixed bootloader", LogType.FileAndConsole); + LogFile.Log("The phone is left in Mass Storage mode", LogType.FileAndConsole); + LogFile.Log("Press and hold the power-button of the phone for at least 10 seconds to reset the phone", LogType.FileAndConsole); + ExitSuccess("Fixed bootloader!", "The phone is left in Mass Storage mode. Press and hold the power-button of the phone for at least 10 seconds to reset the phone."); + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + ExitFailure(Ex.Message, Ex is WPinternalsException ? ((WPinternalsException)Ex).SubMessage : null); + } + + LogFile.EndAction("FixBoot"); + } + + // Assumes phone with Flash protocol v2 + // Assumes phone is in flash mode + internal async static Task LumiaV2FlashPartitions(PhoneNotifierViewModel Notifier, string EFIESPPath, string MainOSPath, string DataPath, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null) + { + NokiaFlashModel FlashModel = (NokiaFlashModel)Notifier.CurrentModel; + + // Use GetGptChunk() here instead of ReadGPT(), because ReadGPT() skips the first sector. + // We need the fist sector if we want to write back the GPT. + byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(FlashModel, 0x20000); + GPT GPT = new(GPTChunk); + + Partition Target; + FlashPart Part; + List Parts = new(); + ulong MainOSOldSectorCount = 0; + ulong MainOSNewSectorCount = 0; + ulong DataOldSectorCount = 0; + ulong DataNewSectorCount = 0; + ulong FirstMainOSSector = 0; + int PartitionCount = 0; + ulong LengthInSectors; + FileInfo FileInfo; + Partition Partition; + bool IsUnlocked = false; + bool GPTChanged = false; + bool ClearFlashingStatus = true; + + if (SetWorkingStatus == null) + { + SetWorkingStatus = (m, s, v, a, st) => { }; + } + + if (UpdateWorkingStatus == null) + { + UpdateWorkingStatus = (m, s, v, st) => { }; + } + + if (ExitSuccess == null) + { + ExitSuccess = (m, s) => { }; + } + + if (ExitFailure == null) + { + ExitFailure = (m, s) => { }; + } + + SetWorkingStatus("Initializing flash...", null, null, Status: WPinternalsStatus.Initializing); + + try + { + if (EFIESPPath != null) + { + FileInfo = new FileInfo(EFIESPPath); + LengthInSectors = (ulong)FileInfo.Length / 0x200; + Partition = GPT.GetPartition("EFIESP"); + if (Partition.SizeInSectors < LengthInSectors) + { + LogFile.Log("Flash failed! Size of partition 'EFIESP' is too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Size of partition 'EFIESP' is too big."); + return; + } + PartitionCount++; + + byte[] EfiespBinary = File.ReadAllBytes(EFIESPPath); + IsUnlocked = (ByteOperations.ReadUInt32(EfiespBinary, 0x20) == (EfiespBinary.Length / 0x200 / 2)) && ByteOperations.ReadAsciiString(EfiespBinary, (UInt32)(EfiespBinary.Length / 2) + 3, 8) == "MSDOS5.0"; + if (IsUnlocked) + { + Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); + if (IsUnlockedFlag == null) + { + IsUnlockedFlag = new Partition + { + Name = "IS_UNLOCKED", + Attributes = 0, + PartitionGuid = Guid.NewGuid(), + PartitionTypeGuid = Guid.NewGuid(), + FirstSector = 0x40, + LastSector = 0x40 + }; + GPT.Partitions.Add(IsUnlockedFlag); + GPTChanged = true; + ClearFlashingStatus = false; + } + } + else + { + Partition IsUnlockedFlag = GPT.GetPartition("IS_UNLOCKED"); + if (IsUnlockedFlag != null) + { + GPT.Partitions.Remove(IsUnlockedFlag); + GPTChanged = true; + ClearFlashingStatus = false; + } + } + } + + if (MainOSPath != null) + { + FileInfo = new FileInfo(MainOSPath); + LengthInSectors = (ulong)FileInfo.Length / 0x200; + Partition = GPT.GetPartition("MainOS"); + MainOSOldSectorCount = Partition.SizeInSectors; + MainOSNewSectorCount = LengthInSectors; + FirstMainOSSector = Partition.FirstSector; + PartitionCount++; + } + + if (DataPath != null) + { + FileInfo = new FileInfo(DataPath); + LengthInSectors = (ulong)FileInfo.Length / 0x200; + Partition = GPT.GetPartition("Data"); + DataOldSectorCount = Partition.SizeInSectors; + DataNewSectorCount = LengthInSectors; + PartitionCount++; + } + + if (PartitionCount > 0) + { + if ((MainOSNewSectorCount > 0) && (DataNewSectorCount > 0)) + { + if ((MainOSNewSectorCount > MainOSOldSectorCount) || (DataNewSectorCount > DataOldSectorCount)) + { + UInt64 OSSpace = GPT.LastUsableSector - FirstMainOSSector + 1; + if ((MainOSNewSectorCount + DataNewSectorCount) <= OSSpace) + { + // MainOS and Data partitions need to be re-aligned! + Partition MainOSPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); + Partition DataPartition = GPT.Partitions.Single(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); + MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1; + DataPartition.FirstSector = MainOSPartition.LastSector + 1; + if ((DataPartition.FirstSector % 0x100) > 0) + { + DataPartition.FirstSector = (DataPartition.FirstSector + 0x100) / 0x100 * 0x100; + } + + DataPartition.LastSector = DataPartition.FirstSector + DataNewSectorCount - 1; + + GPTChanged = true; + } + else + { + LogFile.Log("Flash failed! Sizes of partitions 'MainOS' and 'Data' together are too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Sizes of partitions 'MainOS' and 'Data' together are too big."); + return; + } + } + } + else if ((MainOSNewSectorCount > 0) && (MainOSNewSectorCount > MainOSOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'MainOS' is too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Size of partition 'MainOS' is too big."); + return; + } + else if ((DataNewSectorCount > 0) && (DataNewSectorCount > DataOldSectorCount)) + { + LogFile.Log("Flash failed! Size of partition 'Data' is too big.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "Size of partition 'Data' is too big."); + return; + } + + if (!ClearFlashingStatus) + { + if (!IsUnlocked) + { + // Undo secure boot exploit + Partition NvBackupPartition = GPT.GetPartition("BACKUP_BS_NV"); + if (NvBackupPartition != null) + { + // This must be a left over of a half unlocked bootloader + Partition NvPartition = GPT.GetPartition("UEFI_BS_NV"); + NvBackupPartition.Name = "UEFI_BS_NV"; + NvBackupPartition.PartitionGuid = NvPartition.PartitionGuid; + NvBackupPartition.PartitionTypeGuid = NvPartition.PartitionTypeGuid; + GPT.Partitions.Remove(NvPartition); + GPTChanged = true; + } + + PhoneInfo Info = FlashModel.ReadPhoneInfo(false); + + // We should only clear NV if there was no backup NV to be restored and the current NV contains the SB unlock. + if ((NvBackupPartition == null) && !Info.UefiSecureBootEnabled) + { + // ClearNV + Part = new FlashPart(); + Partition Target2 = GPT.GetPartition("UEFI_BS_NV"); + Part.StartSector = (UInt32)Target2.FirstSector; + Part.Stream = new MemoryStream(new byte[0x40000]); + Parts.Add(Part); + } + } + else + { + // Now add NV partition + Partition BACKUP_BS_NV = GPT.GetPartition("BACKUP_BS_NV"); + Partition UEFI_BS_NV; + if (BACKUP_BS_NV == null) + { + BACKUP_BS_NV = GPT.GetPartition("UEFI_BS_NV"); + Guid OriginalPartitionTypeGuid = BACKUP_BS_NV.PartitionTypeGuid; + Guid OriginalPartitionGuid = BACKUP_BS_NV.PartitionGuid; + BACKUP_BS_NV.Name = "BACKUP_BS_NV"; + BACKUP_BS_NV.PartitionGuid = Guid.NewGuid(); + BACKUP_BS_NV.PartitionTypeGuid = Guid.NewGuid(); + UEFI_BS_NV = new Partition + { + Name = "UEFI_BS_NV", + Attributes = BACKUP_BS_NV.Attributes, + PartitionGuid = OriginalPartitionGuid, + PartitionTypeGuid = OriginalPartitionTypeGuid, + FirstSector = BACKUP_BS_NV.LastSector + 1 + }; + UEFI_BS_NV.LastSector = UEFI_BS_NV.FirstSector + BACKUP_BS_NV.LastSector - BACKUP_BS_NV.FirstSector; + GPT.Partitions.Add(UEFI_BS_NV); + GPTChanged = true; + } + + Part = new FlashPart(); + Target = GPT.GetPartition("UEFI_BS_NV"); + Part.StartSector = (UInt32)Target.FirstSector; // GPT is prepared for 64-bit sector-offset, but flash app isn't. + Part.Stream = new SeekableStream(() => + { + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + + // Magic! + // The SB resource is a compressed version of a raw NV-variable-partition. + // In this partition the SecureBoot variable is disabled. + // It overwrites the variable in a different NV-partition than where this variable is stored usually. + // This normally leads to endless-loops when the NV-variables are enumerated. + // But the partition contains an extra hack to break out the endless loops. + var stream = assembly.GetManifestResourceStream("WPinternals.SB"); + + return new DecompressedStream(stream); + }); + Parts.Add(Part); + } + } + + if (GPTChanged) + { + GPT.Rebuild(); + Part = new FlashPart + { + StartSector = 0, + Stream = new MemoryStream(GPTChunk) + }; + Parts.Add(Part); + } + + int Count = 0; + + Target = GPT.Partitions.Find(p => string.Equals(p.Name, "EFIESP", StringComparison.CurrentCultureIgnoreCase)); + if ((EFIESPPath != null) && (Target != null)) + { + Count++; + Part = new FlashPart + { + StartSector = (UInt32)Target.FirstSector, + Stream = new FileStream(EFIESPPath, FileMode.Open), + ProgressText = "Flashing partition EFIESP (" + Count.ToString() + " / " + PartitionCount.ToString() + ")" + }; + Parts.Add(Part); + LogFile.Log("Partition name=EFIESP, startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Part.Stream.Length / 0x200).ToString("X8"), LogType.FileOnly); + } + + Target = GPT.Partitions.Find(p => string.Equals(p.Name, "MainOS", StringComparison.CurrentCultureIgnoreCase)); + if ((MainOSPath != null) && (Target != null)) + { + Count++; + Part = new FlashPart + { + StartSector = (UInt32)Target.FirstSector, + Stream = new FileStream(MainOSPath, FileMode.Open), + ProgressText = "Flashing partition MainOS (" + Count.ToString() + " / " + PartitionCount.ToString() + ")" + }; + Parts.Add(Part); + LogFile.Log("Partition name=MainOS, startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Part.Stream.Length / 0x200).ToString("X8"), LogType.FileOnly); + } + + Target = GPT.Partitions.Find(p => string.Equals(p.Name, "Data", StringComparison.CurrentCultureIgnoreCase)); + if ((DataPath != null) && (Target != null)) + { + Count++; + Part = new FlashPart + { + StartSector = (UInt32)Target.FirstSector, + Stream = new FileStream(DataPath, FileMode.Open), + ProgressText = "Flashing partition Data (" + Count.ToString() + " / " + PartitionCount.ToString() + ")" + }; + Parts.Add(Part); + LogFile.Log("Partition name=Data, startsector=0x" + Target.FirstSector.ToString("X8") + ", sectorcount = 0x" + (Part.Stream.Length / 0x200).ToString("X8"), LogType.FileOnly); + } + + // Do actual flashing! + await LumiaV2CustomFlash(Notifier, null, false, false, Parts, true, ClearFlashingStatus, false, true, false, SetWorkingStatus, UpdateWorkingStatus, ExitSuccess, ExitFailure); + } + else + { + LogFile.Log("Flash failed! No valid partitions found in the archive.", LogType.FileAndConsole); + ExitFailure("Flash failed!", "No valid partitions found in the archive"); + return; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + ExitFailure(Ex.Message, Ex is WPinternalsException ? ((WPinternalsException)Ex).SubMessage : null); + return; + } + } + } + + internal static class UefiMemorySim + { + internal const UInt32 PageSize = 0x1000; + + private static UInt32 CurrentBufferSize = 0; + public static byte[] Buffer; + private static readonly List Allocations = new(); + private static readonly List FreeMemRanges = new(); + + public static UInt32 RoundUpToPages(UInt32 Size) + { + UInt32 Result = Size + 0x18; + if ((Size % PageSize) != 0) + { + Size = ((Size / PageSize) + 1) * PageSize; + } + + return Result; + } + + public static void Reset() + { + CurrentBufferSize = 0; + Buffer = null; + Allocations.Clear(); + FreeMemRanges.Clear(); + } + + private static void ExtendBuffer(UInt32 Size) + { + byte[] NewBuffer = new byte[CurrentBufferSize + Size]; + if (CurrentBufferSize > 0) + { + System.Buffer.BlockCopy(Buffer, 0, NewBuffer, (int)Size, (int)CurrentBufferSize); + } + + foreach (Allocation CurrentAllocation in Allocations) + { + CurrentAllocation.TotalStart += Size; + CurrentAllocation.TotalEnd += Size; + CurrentAllocation.HeadStart += Size; + CurrentAllocation.HeadEnd += Size; + CurrentAllocation.ContentStart += Size; + CurrentAllocation.ContentEnd += Size; + CurrentAllocation.TailStart += Size; + CurrentAllocation.TailEnd += Size; + } + foreach (FreeMemRange CurrentRange in FreeMemRanges) + { + CurrentRange.Start += Size; + CurrentRange.End += Size; + } + CurrentBufferSize += Size; + Buffer = NewBuffer; + } + + internal static Allocation AllocatePages(UInt32 Size) + { + Allocation NewAllocation = null; + + UInt32 TotalSize = Size; + + if ((TotalSize % PageSize) != 0) + { + throw new NotSupportedException("Wrong allocation size"); + } + else + { + for (int i = FreeMemRanges.Count - 1; i >= 0; i--) + { + if (FreeMemRanges[i].Size >= TotalSize) + { + NewAllocation = new Allocation + { + TotalStart = FreeMemRanges[i].End - TotalSize + 1 + }; + + if (FreeMemRanges[i].Size == TotalSize) + { + FreeMemRanges.RemoveAt(i); + } + else + { + FreeMemRanges[i].End -= TotalSize; + } + + break; + } + } + + if (NewAllocation == null) + { + uint FreeBuffer = Allocations.Count > 0 ? Allocations[0].TotalStart : CurrentBufferSize; + if (FreeBuffer < TotalSize) + { + ExtendBuffer(TotalSize - FreeBuffer); + } + + NewAllocation = new Allocation(); + + if (Allocations.Count > 0) + { + NewAllocation.TotalStart = Allocations[0].TotalStart - TotalSize; + } + else + { + FreeBuffer = CurrentBufferSize - TotalSize; + } + } + + bool Added = false; + for (int i = 0; i < Allocations.Count; i++) + { + if (NewAllocation.TotalStart < Allocations[i].TotalStart) + { + Allocations.Insert(i, NewAllocation); + Added = true; + break; + } + } + if (!Added) + { + Allocations.Add(NewAllocation); + } + } + + return NewAllocation; + } + + internal static Allocation AllocatePool(UInt32 Size) + { + Allocation NewAllocation = null; + + UInt32 TotalSize = Size + 24; + + if (TotalSize < PageSize) + { + throw new NotSupportedException("Allocation of small memory area's is not supported by this UEFI memory management simulator"); + } + else + { + if ((TotalSize % PageSize) != 0) + { + TotalSize = ((TotalSize / PageSize) + 1) * PageSize; + } + + for (int i = FreeMemRanges.Count - 1; i >= 0; i--) + { + if (FreeMemRanges[i].Size >= TotalSize) + { + NewAllocation = new Allocation + { + TotalStart = FreeMemRanges[i].End - TotalSize + 1 + }; + + if (FreeMemRanges[i].Size == TotalSize) + { + FreeMemRanges.RemoveAt(i); + } + else + { + FreeMemRanges[i].End -= TotalSize; + } + + break; + } + } + + if (NewAllocation == null) + { + uint FreeBuffer = Allocations.Count > 0 ? Allocations[0].TotalStart : CurrentBufferSize; + if (FreeBuffer < TotalSize) + { + ExtendBuffer(TotalSize - FreeBuffer); + } + + NewAllocation = new Allocation(); + + if (Allocations.Count > 0) + { + NewAllocation.TotalStart = Allocations[0].TotalStart - TotalSize; + } + else + { + FreeBuffer = CurrentBufferSize - TotalSize; + } + } + + NewAllocation.TotalEnd = NewAllocation.TotalStart + TotalSize - 1; + NewAllocation.HeadStart = NewAllocation.TotalStart; + NewAllocation.HeadEnd = NewAllocation.HeadStart + 16 - 1; + NewAllocation.ContentStart = NewAllocation.HeadEnd + 1; + NewAllocation.ContentEnd = NewAllocation.ContentStart + Size - 1; + NewAllocation.TailStart = NewAllocation.ContentEnd + 1; + NewAllocation.TailEnd = NewAllocation.TailStart + 8 - 1; + + ByteOperations.WriteAsciiString(Buffer, NewAllocation.HeadStart + 0x00, "phd0"); + + // Correct value here is: Size + 24 + // Wrong value is: TotalSize + // Having correct value avoids memory errors and phone can reboot normally, but NV vars might be written (and that will overwrite the NV vars we wrote ourselves). + // Wrong value will make phone reboot to emergency boot and it makes the phone crash when you want to flash in multiple phases, but it will avoid writing NV vars. + ByteOperations.WriteUInt32(Buffer, NewAllocation.HeadStart + 0x04, Size + 24); + + ByteOperations.WriteUInt32(Buffer, NewAllocation.HeadStart + 0x08, 0x04); // EfiBootServicesData = 0x04 + ByteOperations.WriteUInt32(Buffer, NewAllocation.HeadStart + 0x0C, 0x00); // Reserved + + ByteOperations.WriteAsciiString(Buffer, NewAllocation.TailStart + 0x00, "ptal"); + + // Correct value here is: Size + 24 + // Wrong value is: TotalSize + // Having correct value avoids memory errors and phone can reboot normally, but NV vars might be written (and that will overwrite the NV vars we wrote ourselves). + // Wrong value will make phone reboot to emergency boot and it makes the phone crash when you want to flash in multiple phases, but it will avoid writing NV vars. + ByteOperations.WriteUInt32(Buffer, NewAllocation.TailStart + 0x04, Size + 24); + + bool Added = false; + for (int i = 0; i < Allocations.Count; i++) + { + if (NewAllocation.TotalStart < Allocations[i].TotalStart) + { + Allocations.Insert(i, NewAllocation); + Added = true; + break; + } + } + if (!Added) + { + Allocations.Add(NewAllocation); + } + } + + return NewAllocation; + } + + internal static void FreePool(Allocation Allocation) + { + if (Allocations.Contains(Allocation)) + { + Allocations.Remove(Allocation); + + if (Allocations.Count == 0) + { + FreeMemRanges.Clear(); + } + else + { + FreeMemRange NewFreeRange = new(); + NewFreeRange.Start = Allocation.TotalStart; + NewFreeRange.End = Allocation.TotalEnd; + + bool Added = false; + int i; + for (i = 0; i < FreeMemRanges.Count; i++) + { + if (NewFreeRange.Start < FreeMemRanges[i].Start) + { + FreeMemRanges.Insert(i, NewFreeRange); + Added = true; + break; + } + } + if (!Added) + { + FreeMemRanges.Add(NewFreeRange); + i = FreeMemRanges.Count; + } + + if ((i > 0) && (FreeMemRanges[i].Start == (FreeMemRanges[i - 1].End + 1))) + { + FreeMemRanges[i - 1].End = FreeMemRanges[i].End; + FreeMemRanges.RemoveAt(i); + i--; + } + + if ((i < (FreeMemRanges.Count - 1)) && (FreeMemRanges[i].End == (FreeMemRanges[i - 1].Start - 1))) + { + FreeMemRanges[i].End = FreeMemRanges[i + 1].End; + FreeMemRanges.RemoveAt(i + 1); + } + + if ((Allocations.Count > 0) && (FreeMemRanges[i].Start < Allocations[0].TotalStart)) + { + FreeMemRanges.RemoveAt(i); + } + } + } + } + } + + internal class Allocation + { + public UInt32 TotalStart; + public UInt32 TotalEnd; + public UInt32 ContentStart; + public UInt32 ContentEnd; + public UInt32 HeadStart; + public UInt32 HeadEnd; + public UInt32 TailStart; + public UInt32 TailEnd; + + public UInt32 TotalSize + { + get + { + return TotalEnd - TotalStart + 1; + } + } + + public UInt32 ContentSize + { + get + { + return ContentEnd - ContentStart + 1; + } + } + + public void CopyFromThisAllocation(UInt32 ContentOffset, UInt32 Size, byte[] Destination, UInt32 DestinationOffset) + { + Buffer.BlockCopy(UefiMemorySim.Buffer, (int)(ContentStart + ContentOffset), Destination, (int)DestinationOffset, (int)Size); + } + + public void CopyToThisAllocation(byte[] Source, UInt32 SourceOffset, UInt32 Size, UInt32 ContentOffset) + { + Buffer.BlockCopy(Source, (int)SourceOffset, UefiMemorySim.Buffer, (int)(ContentStart + ContentOffset), (int)Size); + } + } + + internal class FreeMemRange + { + public UInt32 Start; + public UInt32 End; + + public UInt32 Size + { + get + { + return End - Start + 1; + } + } + } + + internal class FlashPart + { + public string ProgressText; + public UInt32 StartSector; + public Stream Stream; + } +} diff --git a/ViewModels/LumiaV3FlashRomViewModel.cs b/WPinternals/ViewModels/LumiaV3FlashRomViewModel.cs similarity index 100% rename from ViewModels/LumiaV3FlashRomViewModel.cs rename to WPinternals/ViewModels/LumiaV3FlashRomViewModel.cs diff --git a/ViewModels/MainViewModel.cs b/WPinternals/ViewModels/MainViewModel.cs similarity index 97% rename from ViewModels/MainViewModel.cs rename to WPinternals/ViewModels/MainViewModel.cs index f49dd96..c643abf 100644 --- a/ViewModels/MainViewModel.cs +++ b/WPinternals/ViewModels/MainViewModel.cs @@ -1,485 +1,485 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using Microsoft.Win32; -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Input; - -namespace WPinternals -{ - public enum NavigationSubject - { - Info, - Mode, - Install, - About - }; - - public enum PhoneInterfaces - { - Lumia_Normal, - Lumia_Flash, - Lumia_Label, - Lumia_MassStorage, - Lumia_Bootloader, - Qualcomm_Download, - Qualcomm_Flash, - Lumia_BadMassStorage - }; - - // Create this class on the UI thread, after the main-window of the application is initialized. - // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. - // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. - // Therefore the main window must be created before the ViewModel is created. - internal class MainViewModel : INotifyPropertyChanged - { - public PhoneInterfaces? CurrentInterface = null; - public PhoneInterfaces? LastInterface = null; - public NokiaPhoneModel CurrentModel = null; - public PhoneNotifierViewModel PhoneNotifier; - public LumiaInfoViewModel InfoViewModel; - public LumiaModeViewModel ModeViewModel; - public LumiaUnlockBootViewModel BootUnlockViewModel; - public LumiaUnlockBootViewModel BootRestoreViewModel; - public LumiaUnlockRootViewModel RootUnlockViewModel; - public LumiaUnlockRootViewModel RootRestoreViewModel; - public BackupViewModel BackupViewModel; - public RestoreViewModel RestoreViewModel; - public LumiaFlashRomViewModel LumiaFlashRomViewModel; - public DumpRomViewModel DumpRomViewModel; - public DownloadsViewModel DownloadsViewModel; - private GettingStartedViewModel _GettingStartedViewModel = null; - - public event PropertyChangedEventHandler PropertyChanged; - private void OnPropertyChanged(string propertyName) - { - if (this.PropertyChanged != null) - { - if (MainSyncContext == SynchronizationContext.Current) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } - else - { - MainSyncContext.Post(s => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); - } - } - } - - private ContextViewModel _ContextViewModel; - public ContextViewModel ContextViewModel - { - get - { - return _ContextViewModel; - } - set - { - if (_ContextViewModel != value) - { - if (_ContextViewModel != null) - { - _ContextViewModel.IsActive = false; - } - - _ContextViewModel = value; - _ContextViewModel?.Activate(); - OnPropertyChanged(nameof(ContextViewModel)); - } - } - } - - private readonly SynchronizationContext MainSyncContext; - - public MainViewModel() - { - MainSyncContext = SynchronizationContext.Current; - - LogFile.LogApplicationVersion(); - - // Set global callback for cases where Dependency Injection is not possible. - App.NavigateToGettingStarted = () => GettingStartedCommand.Execute(null); - App.NavigateToUnlockBoot = () => BootUnlockCommand.Execute(null); - - if (Registry.CurrentUser.OpenSubKey("Software\\WPInternals") == null) - { - Registry.CurrentUser.OpenSubKey("Software", true).CreateSubKey("WPInternals"); - } - - if (Registration.IsPrerelease && (Registry.CurrentUser.OpenSubKey("Software\\WPInternals").GetValue("NdaAccepted") == null)) - { - this.ContextViewModel = new DisclaimerAndNdaViewModel(Disclaimer_Accepted); - } - else if (Registry.CurrentUser.OpenSubKey("Software\\WPInternals").GetValue("DisclaimerAccepted") == null) - { - this.ContextViewModel = new DisclaimerViewModel(Disclaimer_Accepted); - } - else if (Registration.IsPrerelease && !Registration.IsRegistered()) - { - ContextViewModel = new RegistrationViewModel(Registration_Completed, Registration_Failed); - } - else - { - StartOperation(); - } - } - - private void Disclaimer_Accepted() - { - ContextViewModel = null; - - if (Registration.IsPrerelease && !Registration.IsRegistered()) - { - ContextViewModel = new RegistrationViewModel(Registration_Completed, Registration_Failed); - } - else - { - StartOperation(); - } - } - - private void Registration_Completed() - { - ContextViewModel = null; - StartOperation(); - } - - private void Registration_Failed() - { - ContextViewModel = new MessageViewModel("Registration failed", () => Environment.Exit(0)); - ((MessageViewModel)ContextViewModel).SubMessage = "Check your filewall settings"; - } - - public void StartOperation() - { - IsMenuEnabled = true; - - _GettingStartedViewModel = new GettingStartedViewModel( - () => - { - ContextViewModel = new DisclaimerViewModel( - () => ContextViewModel = _GettingStartedViewModel - ); - }, - SwitchToUnlockBoot, - SwitchToUnlockRoot, - SwitchToBackup, - SwitchToDumpFFU, - SwitchToFlashRom, - SwitchToDownload - ); - this.ContextViewModel = _GettingStartedViewModel; - - PhoneNotifier = new PhoneNotifierViewModel(); - PhoneNotifier.NewDeviceArrived += PhoneNotifier_NewDeviceArrived; - PhoneNotifier.DeviceRemoved += PhoneNotifier_DeviceRemoved; - - InfoViewModel = new LumiaInfoViewModel(PhoneNotifier, (TargetInterface) => - { - ModeViewModel.OnModeSwitchRequested(TargetInterface); - ContextViewModel = ModeViewModel; - }, - () => ContextViewModel = _GettingStartedViewModel); - InfoViewModel.ActivateSubContext(null); - - ModeViewModel = new LumiaModeViewModel(PhoneNotifier, SwitchToInfoViewModel); - ModeViewModel.ActivateSubContext(null); - - BootUnlockViewModel = new LumiaUnlockBootViewModel(PhoneNotifier, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, true, SwitchToInfoViewModel); - BootUnlockViewModel.ActivateSubContext(null); - - BootRestoreViewModel = new LumiaUnlockBootViewModel(PhoneNotifier, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, false, SwitchToInfoViewModel); - BootRestoreViewModel.ActivateSubContext(null); - - RootUnlockViewModel = new LumiaUnlockRootViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpFFU, SwitchToFlashRom, true, SwitchToInfoViewModel); - RootUnlockViewModel.ActivateSubContext(null); - - RootRestoreViewModel = new LumiaUnlockRootViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpFFU, SwitchToFlashRom, false, SwitchToInfoViewModel); - RootRestoreViewModel.ActivateSubContext(null); - - BackupViewModel = new BackupViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToInfoViewModel); - BackupViewModel.ActivateSubContext(null); - - RestoreViewModel = new RestoreViewModel(PhoneNotifier, SwitchToDifferentInterface, SwitchToUnlockBoot, SwitchToFlashRom, SwitchToInfoViewModel); - RestoreViewModel.ActivateSubContext(null); - - LumiaFlashRomViewModel = new LumiaFlashRomViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToDumpFFU, SwitchToBackup, SwitchToInfoViewModel); - LumiaFlashRomViewModel.ActivateSubContext(null); - - DumpRomViewModel = new DumpRomViewModel(SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToFlashRom); - DumpRomViewModel.ActivateSubContext(null); - - DownloadsViewModel = new DownloadsViewModel(PhoneNotifier); - App.DownloadManager = DownloadsViewModel; - - PhoneNotifier.Start(); - } - - internal void SwitchToInfoViewModel() - { - ContextViewModel = InfoViewModel; - } - - internal void SwitchToUnlockBoot() - { - ContextViewModel = BootUnlockViewModel; - } - - internal void SwitchToRestoreBoot() - { - ContextViewModel = BootRestoreViewModel; - } - - internal void SwitchToUnlockRoot() - { - ContextViewModel = RootUnlockViewModel; - } - - internal void SwitchToUndoRoot() - { - ContextViewModel = RootRestoreViewModel; - } - - internal void SwitchToBackup() - { - ContextViewModel = BackupViewModel; - } - - internal void SwitchToFlashRom() - { - ContextViewModel = LumiaFlashRomViewModel; - } - - internal void SwitchToDumpFFU() - { - ContextViewModel = DumpRomViewModel; - } - - internal void SwitchToDownload() - { - ContextViewModel = DownloadsViewModel; - } - - internal void SwitchToDifferentInterface(PhoneInterfaces TargetInterface) - { - ModeViewModel.OnModeSwitchRequested(TargetInterface); - ContextViewModel = ModeViewModel; - } - - private void PhoneNotifier_DeviceRemoved() - { - InfoViewModel.ActivateSubContext(null); - } - - private void PhoneNotifier_NewDeviceArrived(ArrivalEventArgs Args) - { - PhoneInterfaces? PreviousInterface = LastInterface; - LastInterface = Args.NewInterface; - - if (App.InterruptBoot && (Args.NewInterface == PhoneInterfaces.Lumia_Bootloader)) - { - App.InterruptBoot = false; - LogFile.Log("Found Lumia BootMgr and user forced to interrupt the boot process. Force to Flash-mode."); - Task.Run(() => SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Lumia_Flash)); - } - else - { - if (Args.NewInterface != PhoneInterfaces.Qualcomm_Download) - { - App.InterruptBoot = false; - } - - if (ContextViewModel == null) - { - ContextViewModel = InfoViewModel; - } - else if (ContextViewModel.IsFlashModeOperation) - { - if ((!ContextViewModel.IsSwitchingInterface) && (Args.NewInterface == PhoneInterfaces.Lumia_Bootloader)) - { - // The current screen is marked as "Flash operation". - // When the bootloader is detected at this stage, it means a phone is booting and - // it is possible that the phone is in a non-booting stage (not possible to boot past UEFI). - // We will try to boot straight to Flash-mode, so that it will be possible to flash a new ROM. - LogFile.Log("Found Lumia BootMgr while mode is not being switched. Screen is marked as Flash Operation. Force to Flash-mode."); - Task.Run(() => SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Lumia_Flash)); - } - } - else - { - if ((!ContextViewModel.IsSwitchingInterface) && (Args.NewInterface != PhoneInterfaces.Lumia_Bootloader)) - { - ContextViewModel = InfoViewModel; - } - } - } - } - - private ICommand _InfoCommand = null; - public ICommand InfoCommand - { - get - { - return _InfoCommand ??= new DelegateCommand(() => ContextViewModel = InfoViewModel); - } - } - - private ICommand _ModeCommand = null; - public ICommand ModeCommand - { - get - { - return _ModeCommand ??= new DelegateCommand(() => ContextViewModel = ModeViewModel); - } - } - - private ICommand _BootUnlockCommand = null; - public ICommand BootUnlockCommand - { - get - { - return _BootUnlockCommand ??= new DelegateCommand(() => ContextViewModel = BootUnlockViewModel); - } - } - - private ICommand _BootRestoreCommand = null; - public ICommand BootRestoreCommand - { - get - { - return _BootRestoreCommand ??= new DelegateCommand(() => ContextViewModel = BootRestoreViewModel); - } - } - - private ICommand _RootUnlockCommand = null; - public ICommand RootUnlockCommand - { - get - { - return _RootUnlockCommand ??= new DelegateCommand(() => ContextViewModel = RootUnlockViewModel); - } - } - - private ICommand _RootUndoCommand = null; - public ICommand RootUndoCommand - { - get - { - return _RootUndoCommand ??= new DelegateCommand(() => ContextViewModel = RootRestoreViewModel); - } - } - - private ICommand _BackupCommand = null; - public ICommand BackupCommand - { - get - { - return _BackupCommand ??= new DelegateCommand(() => ContextViewModel = BackupViewModel); - } - } - - private ICommand _RestoreCommand = null; - public ICommand RestoreCommand - { - get - { - return _RestoreCommand ??= new DelegateCommand(() => ContextViewModel = RestoreViewModel); - } - } - - private ICommand _LumiaFlashRomCommand = null; - public ICommand LumiaFlashRomCommand - { - get - { - return _LumiaFlashRomCommand ??= new DelegateCommand(() => ContextViewModel = LumiaFlashRomViewModel); - } - } - - private ICommand _DumpRomCommand = null; - public ICommand DumpRomCommand - { - get - { - return _DumpRomCommand ??= new DelegateCommand(() => ContextViewModel = DumpRomViewModel); - } - } - - private ICommand _AboutCommand = null; - public ICommand AboutCommand - { - get - { - return _AboutCommand ??= new DelegateCommand(() => ContextViewModel = new AboutViewModel()); - } - } - - private ICommand _DonateCommand = null; - public ICommand DonateCommand - { - get - { - return _DonateCommand ??= new DelegateCommand(() => - { - Process process = new(); - process.StartInfo.UseShellExecute = true; - process.StartInfo.FileName = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VY8N7BCBT9CS4"; - process.Start(); - }); - } - } - - private ICommand _GettingStartedCommand = null; - public ICommand GettingStartedCommand - { - get - { - return _GettingStartedCommand ??= new DelegateCommand(() => ContextViewModel = _GettingStartedViewModel); - } - } - - private ICommand _DownloadCommand = null; - public ICommand DownloadCommand - { - get - { - return _DownloadCommand ??= new DelegateCommand(() => ContextViewModel = DownloadsViewModel); - } - } - - private bool _IsMenuEnabled = false; - public bool IsMenuEnabled - { - get - { - return _IsMenuEnabled; - } - set - { - _IsMenuEnabled = value; - OnPropertyChanged(nameof(IsMenuEnabled)); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace WPinternals +{ + public enum NavigationSubject + { + Info, + Mode, + Install, + About + }; + + public enum PhoneInterfaces + { + Lumia_Normal, + Lumia_Flash, + Lumia_Label, + Lumia_MassStorage, + Lumia_Bootloader, + Qualcomm_Download, + Qualcomm_Flash, + Lumia_BadMassStorage + }; + + // Create this class on the UI thread, after the main-window of the application is initialized. + // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. + // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. + // Therefore the main window must be created before the ViewModel is created. + internal class MainViewModel : INotifyPropertyChanged + { + public PhoneInterfaces? CurrentInterface = null; + public PhoneInterfaces? LastInterface = null; + public NokiaPhoneModel CurrentModel = null; + public PhoneNotifierViewModel PhoneNotifier; + public LumiaInfoViewModel InfoViewModel; + public LumiaModeViewModel ModeViewModel; + public LumiaUnlockBootViewModel BootUnlockViewModel; + public LumiaUnlockBootViewModel BootRestoreViewModel; + public LumiaUnlockRootViewModel RootUnlockViewModel; + public LumiaUnlockRootViewModel RootRestoreViewModel; + public BackupViewModel BackupViewModel; + public RestoreViewModel RestoreViewModel; + public LumiaFlashRomViewModel LumiaFlashRomViewModel; + public DumpRomViewModel DumpRomViewModel; + public DownloadsViewModel DownloadsViewModel; + private GettingStartedViewModel _GettingStartedViewModel = null; + + public event PropertyChangedEventHandler PropertyChanged; + private void OnPropertyChanged(string propertyName) + { + if (this.PropertyChanged != null) + { + if (MainSyncContext == SynchronizationContext.Current) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + else + { + MainSyncContext.Post(s => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null); + } + } + } + + private ContextViewModel _ContextViewModel; + public ContextViewModel ContextViewModel + { + get + { + return _ContextViewModel; + } + set + { + if (_ContextViewModel != value) + { + if (_ContextViewModel != null) + { + _ContextViewModel.IsActive = false; + } + + _ContextViewModel = value; + _ContextViewModel?.Activate(); + OnPropertyChanged(nameof(ContextViewModel)); + } + } + } + + private readonly SynchronizationContext MainSyncContext; + + public MainViewModel() + { + MainSyncContext = SynchronizationContext.Current; + + LogFile.LogApplicationVersion(); + + // Set global callback for cases where Dependency Injection is not possible. + App.NavigateToGettingStarted = () => GettingStartedCommand.Execute(null); + App.NavigateToUnlockBoot = () => BootUnlockCommand.Execute(null); + + if (Registry.CurrentUser.OpenSubKey("Software\\WPInternals") == null) + { + Registry.CurrentUser.OpenSubKey("Software", true).CreateSubKey("WPInternals"); + } + + if (Registration.IsPrerelease && (Registry.CurrentUser.OpenSubKey("Software\\WPInternals").GetValue("NdaAccepted") == null)) + { + this.ContextViewModel = new DisclaimerAndNdaViewModel(Disclaimer_Accepted); + } + else if (Registry.CurrentUser.OpenSubKey("Software\\WPInternals").GetValue("DisclaimerAccepted") == null) + { + this.ContextViewModel = new DisclaimerViewModel(Disclaimer_Accepted); + } + else if (Registration.IsPrerelease && !Registration.IsRegistered()) + { + ContextViewModel = new RegistrationViewModel(Registration_Completed, Registration_Failed); + } + else + { + StartOperation(); + } + } + + private void Disclaimer_Accepted() + { + ContextViewModel = null; + + if (Registration.IsPrerelease && !Registration.IsRegistered()) + { + ContextViewModel = new RegistrationViewModel(Registration_Completed, Registration_Failed); + } + else + { + StartOperation(); + } + } + + private void Registration_Completed() + { + ContextViewModel = null; + StartOperation(); + } + + private void Registration_Failed() + { + ContextViewModel = new MessageViewModel("Registration failed", () => Environment.Exit(0)); + ((MessageViewModel)ContextViewModel).SubMessage = "Check your filewall settings"; + } + + public void StartOperation() + { + IsMenuEnabled = true; + + _GettingStartedViewModel = new GettingStartedViewModel( + () => + { + ContextViewModel = new DisclaimerViewModel( + () => ContextViewModel = _GettingStartedViewModel + ); + }, + SwitchToUnlockBoot, + SwitchToUnlockRoot, + SwitchToBackup, + SwitchToDumpFFU, + SwitchToFlashRom, + SwitchToDownload + ); + this.ContextViewModel = _GettingStartedViewModel; + + PhoneNotifier = new PhoneNotifierViewModel(); + PhoneNotifier.NewDeviceArrived += PhoneNotifier_NewDeviceArrived; + PhoneNotifier.DeviceRemoved += PhoneNotifier_DeviceRemoved; + + InfoViewModel = new LumiaInfoViewModel(PhoneNotifier, (TargetInterface) => + { + ModeViewModel.OnModeSwitchRequested(TargetInterface); + ContextViewModel = ModeViewModel; + }, + () => ContextViewModel = _GettingStartedViewModel); + InfoViewModel.ActivateSubContext(null); + + ModeViewModel = new LumiaModeViewModel(PhoneNotifier, SwitchToInfoViewModel); + ModeViewModel.ActivateSubContext(null); + + BootUnlockViewModel = new LumiaUnlockBootViewModel(PhoneNotifier, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, true, SwitchToInfoViewModel); + BootUnlockViewModel.ActivateSubContext(null); + + BootRestoreViewModel = new LumiaUnlockBootViewModel(PhoneNotifier, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, false, SwitchToInfoViewModel); + BootRestoreViewModel.ActivateSubContext(null); + + RootUnlockViewModel = new LumiaUnlockRootViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpFFU, SwitchToFlashRom, true, SwitchToInfoViewModel); + RootUnlockViewModel.ActivateSubContext(null); + + RootRestoreViewModel = new LumiaUnlockRootViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToDumpFFU, SwitchToFlashRom, false, SwitchToInfoViewModel); + RootRestoreViewModel.ActivateSubContext(null); + + BackupViewModel = new BackupViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToInfoViewModel); + BackupViewModel.ActivateSubContext(null); + + RestoreViewModel = new RestoreViewModel(PhoneNotifier, SwitchToDifferentInterface, SwitchToUnlockBoot, SwitchToFlashRom, SwitchToInfoViewModel); + RestoreViewModel.ActivateSubContext(null); + + LumiaFlashRomViewModel = new LumiaFlashRomViewModel(PhoneNotifier, SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToDumpFFU, SwitchToBackup, SwitchToInfoViewModel); + LumiaFlashRomViewModel.ActivateSubContext(null); + + DumpRomViewModel = new DumpRomViewModel(SwitchToUnlockBoot, SwitchToUnlockRoot, SwitchToFlashRom); + DumpRomViewModel.ActivateSubContext(null); + + DownloadsViewModel = new DownloadsViewModel(PhoneNotifier); + App.DownloadManager = DownloadsViewModel; + + PhoneNotifier.Start(); + } + + internal void SwitchToInfoViewModel() + { + ContextViewModel = InfoViewModel; + } + + internal void SwitchToUnlockBoot() + { + ContextViewModel = BootUnlockViewModel; + } + + internal void SwitchToRestoreBoot() + { + ContextViewModel = BootRestoreViewModel; + } + + internal void SwitchToUnlockRoot() + { + ContextViewModel = RootUnlockViewModel; + } + + internal void SwitchToUndoRoot() + { + ContextViewModel = RootRestoreViewModel; + } + + internal void SwitchToBackup() + { + ContextViewModel = BackupViewModel; + } + + internal void SwitchToFlashRom() + { + ContextViewModel = LumiaFlashRomViewModel; + } + + internal void SwitchToDumpFFU() + { + ContextViewModel = DumpRomViewModel; + } + + internal void SwitchToDownload() + { + ContextViewModel = DownloadsViewModel; + } + + internal void SwitchToDifferentInterface(PhoneInterfaces TargetInterface) + { + ModeViewModel.OnModeSwitchRequested(TargetInterface); + ContextViewModel = ModeViewModel; + } + + private void PhoneNotifier_DeviceRemoved() + { + InfoViewModel.ActivateSubContext(null); + } + + private void PhoneNotifier_NewDeviceArrived(ArrivalEventArgs Args) + { + PhoneInterfaces? PreviousInterface = LastInterface; + LastInterface = Args.NewInterface; + + if (App.InterruptBoot && (Args.NewInterface == PhoneInterfaces.Lumia_Bootloader)) + { + App.InterruptBoot = false; + LogFile.Log("Found Lumia BootMgr and user forced to interrupt the boot process. Force to Flash-mode."); + Task.Run(() => SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Lumia_Flash)); + } + else + { + if (Args.NewInterface != PhoneInterfaces.Qualcomm_Download) + { + App.InterruptBoot = false; + } + + if (ContextViewModel == null) + { + ContextViewModel = InfoViewModel; + } + else if (ContextViewModel.IsFlashModeOperation) + { + if ((!ContextViewModel.IsSwitchingInterface) && (Args.NewInterface == PhoneInterfaces.Lumia_Bootloader)) + { + // The current screen is marked as "Flash operation". + // When the bootloader is detected at this stage, it means a phone is booting and + // it is possible that the phone is in a non-booting stage (not possible to boot past UEFI). + // We will try to boot straight to Flash-mode, so that it will be possible to flash a new ROM. + LogFile.Log("Found Lumia BootMgr while mode is not being switched. Screen is marked as Flash Operation. Force to Flash-mode."); + Task.Run(() => SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Lumia_Flash)); + } + } + else + { + if ((!ContextViewModel.IsSwitchingInterface) && (Args.NewInterface != PhoneInterfaces.Lumia_Bootloader)) + { + ContextViewModel = InfoViewModel; + } + } + } + } + + private ICommand _InfoCommand = null; + public ICommand InfoCommand + { + get + { + return _InfoCommand ??= new DelegateCommand(() => ContextViewModel = InfoViewModel); + } + } + + private ICommand _ModeCommand = null; + public ICommand ModeCommand + { + get + { + return _ModeCommand ??= new DelegateCommand(() => ContextViewModel = ModeViewModel); + } + } + + private ICommand _BootUnlockCommand = null; + public ICommand BootUnlockCommand + { + get + { + return _BootUnlockCommand ??= new DelegateCommand(() => ContextViewModel = BootUnlockViewModel); + } + } + + private ICommand _BootRestoreCommand = null; + public ICommand BootRestoreCommand + { + get + { + return _BootRestoreCommand ??= new DelegateCommand(() => ContextViewModel = BootRestoreViewModel); + } + } + + private ICommand _RootUnlockCommand = null; + public ICommand RootUnlockCommand + { + get + { + return _RootUnlockCommand ??= new DelegateCommand(() => ContextViewModel = RootUnlockViewModel); + } + } + + private ICommand _RootUndoCommand = null; + public ICommand RootUndoCommand + { + get + { + return _RootUndoCommand ??= new DelegateCommand(() => ContextViewModel = RootRestoreViewModel); + } + } + + private ICommand _BackupCommand = null; + public ICommand BackupCommand + { + get + { + return _BackupCommand ??= new DelegateCommand(() => ContextViewModel = BackupViewModel); + } + } + + private ICommand _RestoreCommand = null; + public ICommand RestoreCommand + { + get + { + return _RestoreCommand ??= new DelegateCommand(() => ContextViewModel = RestoreViewModel); + } + } + + private ICommand _LumiaFlashRomCommand = null; + public ICommand LumiaFlashRomCommand + { + get + { + return _LumiaFlashRomCommand ??= new DelegateCommand(() => ContextViewModel = LumiaFlashRomViewModel); + } + } + + private ICommand _DumpRomCommand = null; + public ICommand DumpRomCommand + { + get + { + return _DumpRomCommand ??= new DelegateCommand(() => ContextViewModel = DumpRomViewModel); + } + } + + private ICommand _AboutCommand = null; + public ICommand AboutCommand + { + get + { + return _AboutCommand ??= new DelegateCommand(() => ContextViewModel = new AboutViewModel()); + } + } + + private ICommand _DonateCommand = null; + public ICommand DonateCommand + { + get + { + return _DonateCommand ??= new DelegateCommand(() => + { + Process process = new(); + process.StartInfo.UseShellExecute = true; + process.StartInfo.FileName = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VY8N7BCBT9CS4"; + process.Start(); + }); + } + } + + private ICommand _GettingStartedCommand = null; + public ICommand GettingStartedCommand + { + get + { + return _GettingStartedCommand ??= new DelegateCommand(() => ContextViewModel = _GettingStartedViewModel); + } + } + + private ICommand _DownloadCommand = null; + public ICommand DownloadCommand + { + get + { + return _DownloadCommand ??= new DelegateCommand(() => ContextViewModel = DownloadsViewModel); + } + } + + private bool _IsMenuEnabled = false; + public bool IsMenuEnabled + { + get + { + return _IsMenuEnabled; + } + set + { + _IsMenuEnabled = value; + OnPropertyChanged(nameof(IsMenuEnabled)); + } + } + } +} diff --git a/ViewModels/MessageViewModel.cs b/WPinternals/ViewModels/MessageViewModel.cs similarity index 97% rename from ViewModels/MessageViewModel.cs rename to WPinternals/ViewModels/MessageViewModel.cs index 8183868..9ac297c 100644 --- a/ViewModels/MessageViewModel.cs +++ b/WPinternals/ViewModels/MessageViewModel.cs @@ -1,75 +1,75 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class MessageViewModel : ContextViewModel - { - internal MessageViewModel(string Message, Action OkAction = null, Action CancelAction = null) - : base() - { - LogFile.Log(Message); - - if (OkAction != null) - { - this.OkCommand = new DelegateCommand(OkAction); - } - - if (CancelAction != null) - { - this.CancelCommand = new DelegateCommand(CancelAction); - } - - this.Message = Message; - } - - private string _Message = null; - public string Message - { - get - { - return _Message; - } - set - { - _Message = value; - OnPropertyChanged(nameof(Message)); - } - } - - private string _SubMessage = null; - public string SubMessage - { - get - { - return _SubMessage; - } - set - { - _SubMessage = value; - OnPropertyChanged(nameof(SubMessage)); - } - } - public DelegateCommand OkCommand { get; } = null; - public DelegateCommand CancelCommand { get; } = null; - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class MessageViewModel : ContextViewModel + { + internal MessageViewModel(string Message, Action OkAction = null, Action CancelAction = null) + : base() + { + LogFile.Log(Message); + + if (OkAction != null) + { + this.OkCommand = new DelegateCommand(OkAction); + } + + if (CancelAction != null) + { + this.CancelCommand = new DelegateCommand(CancelAction); + } + + this.Message = Message; + } + + private string _Message = null; + public string Message + { + get + { + return _Message; + } + set + { + _Message = value; + OnPropertyChanged(nameof(Message)); + } + } + + private string _SubMessage = null; + public string SubMessage + { + get + { + return _SubMessage; + } + set + { + _SubMessage = value; + OnPropertyChanged(nameof(SubMessage)); + } + } + public DelegateCommand OkCommand { get; } = null; + public DelegateCommand CancelCommand { get; } = null; + } +} diff --git a/ViewModels/NokiaBootloaderViewModel.cs b/WPinternals/ViewModels/NokiaBootloaderViewModel.cs similarity index 100% rename from ViewModels/NokiaBootloaderViewModel.cs rename to WPinternals/ViewModels/NokiaBootloaderViewModel.cs diff --git a/ViewModels/NokiaFlashViewModel.cs b/WPinternals/ViewModels/NokiaFlashViewModel.cs similarity index 97% rename from ViewModels/NokiaFlashViewModel.cs rename to WPinternals/ViewModels/NokiaFlashViewModel.cs index 4937eaa..bd521b5 100644 --- a/ViewModels/NokiaFlashViewModel.cs +++ b/WPinternals/ViewModels/NokiaFlashViewModel.cs @@ -1,762 +1,762 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - // Create this class on the UI thread, after the main-window of the application is initialized. - // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. - // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. - // Therefore the main window must be created before the ViewModel is created. - - internal class NokiaFlashViewModel : ContextViewModel - { - private readonly NokiaFlashModel CurrentModel; - private readonly Action RequestModeSwitch; - internal Action SwitchToGettingStarted; - private readonly object LockDeviceInfo = new(); - private bool DeviceInfoLoaded = false; - - internal NokiaFlashViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch, Action SwitchToGettingStarted) - : base() - { - this.CurrentModel = (NokiaFlashModel)CurrentModel; - this.RequestModeSwitch = RequestModeSwitch; - this.SwitchToGettingStarted = SwitchToGettingStarted; - } - - // Device info should be loaded only one time and only when the ViewModel is active - internal override void EvaluateViewState() - { - if (IsActive) - { - new Thread(() => StartLoadDeviceInfo()).Start(); - } - } - - private void StartLoadDeviceInfo() - { - lock (LockDeviceInfo) - { - if (!DeviceInfoLoaded) - { - try - { - //byte[] Imsi = CurrentModel.ExecuteJsonMethodAsBytes("ReadImsi", "Imsi"); // 9 bytes: 08 29 40 40 ... - //string BatteryLevel = CurrentModel.ExecuteJsonMethodAsString("ReadBatteryLevel", "BatteryLevel"); - //string SystemAsicVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSystemAsicVersion", "SystemAsicVersion"); // 8960 -> Chip SOC version - //string OperatorName = CurrentModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // 000-DK - //string ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 - //string AkVersion = CurrentModel.ExecuteJsonMethodAsString("ReadAkVersion", "AkVersion"); // 9200.10521 - //string BspVersion = CurrentModel.ExecuteJsonMethodAsString("ReadBspVersion", "BspVersion"); // 3051.40000 - //string ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 - //string SecurityMode = CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode"); // Restricted - //string SerialNumber = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", "SerialNumber"); // 356355051883955 = IMEI - //string SwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 - //string ModuleCode = CurrentModel.ExecuteJsonMethodAsString("ReadModuleCode", "ModuleCode"); // 0205137 - //byte[] PublicId = CurrentModel.ExecuteJsonMethodAsBytes("ReadPublicId", "PublicId"); // 0x14 bytes: a5 e5 ... - //string Psn = CurrentModel.ExecuteJsonMethodAsString("ReadPsn", "Psn"); // CEP737370 - //string HwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadHwVersion", "HWVersion"); // 6504 = 6.5.0.4 - //byte[] BtId = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... - //byte[] WlanMacAddress1 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes - //byte[] WlanMacAddress2 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress2"); // same - //byte[] WlanMacAddress3 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress3"); // same - //bool SimlockActive = CurrentModel.ExecuteJsonMethodAsBoolean("ReadSimlockActive", "SimLockActive"); // false - //string ServiceTag = CurrentModel.ExecuteJsonMethodAsString("ReadServiceTag", "ServiceTag"); // error - //byte[] RfChipsetVersion = CurrentModel.ExecuteJsonMethodAsBytes("ReadRfChipsetVersion", "RfChipsetVersion"); // error - //byte[] Meid = CurrentModel.ExecuteJsonMethodAsBytes("ReadMeid", "Meid"); // error - //string Test = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturingData", ""); -> This method is only possible in Label-mode. - - UefiSecurityStatusResponse SecurityStatus = CurrentModel.ReadSecurityStatus(); - - UInt32? FlagsResult = CurrentModel.ReadSecurityFlags(); - UInt32 SecurityFlags = 0; - if (FlagsResult != null) - { - SecurityFlags = (UInt32)CurrentModel.ReadSecurityFlags(); - LogFile.Log("Security flags: 0x" + SecurityFlags.ToString("X8")); - - FinalConfigDakStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Dak); - FinalConfigFastBootStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.FastBoot); - FinalConfigFfuVerifyStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.FfuVerify); - FinalConfigJtagStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Jtag); - FinalConfigOemIdStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.OemId); - FinalConfigProductionDoneStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.ProductionDone); - FinalConfigPublicIdStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.PublicId); - FinalConfigRkhStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Rkh); - FinalConfigRpmWdogStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.RpmWdog); - FinalConfigSecGenStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.SecGen); - FinalConfigSecureBootStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.SecureBoot); - FinalConfigShkStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Shk); - FinalConfigSimlockStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Simlock); - FinalConfigSpdmSecModeStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.SpdmSecMode); - FinalConfigSsmStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Ssm); - } - else - { - LogFile.Log("Security flags could not be read"); - } - - PlatformName = CurrentModel.ReadStringParam("DPI"); - LogFile.Log("Platform Name: " + PlatformName); - - // Some phones do not support the Terminal interface! (928 verizon) - // Instead read param RRKH to get the RKH. - PublicID = null; - byte[] RawPublicID = CurrentModel.ReadParam("PID"); - if (RawPublicID?.Length > 4) - { - PublicID = new byte[RawPublicID.Length - 4]; - Array.Copy(RawPublicID, 4, PublicID, 0, RawPublicID.Length - 4); - LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); - } - else - { - PublicID = new byte[20]; - LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); - } - RootKeyHash = CurrentModel.ReadParam("RRKH"); - if (RootKeyHash != null) - { - LogFile.Log("Root Key Hash: " + Converter.ConvertHexToString(RootKeyHash, " ")); - } - - if (SecurityStatus != null) - { - PlatformSecureBootStatus = SecurityStatus.PlatformSecureBootStatus; - LogFile.Log("Platform Secure Boot Status: " + PlatformSecureBootStatus.ToString()); - UefiSecureBootStatus = SecurityStatus.UefiSecureBootStatus; - LogFile.Log("Uefi Secure Boot Status: " + UefiSecureBootStatus.ToString()); - EffectiveSecureBootStatus = SecurityStatus.PlatformSecureBootStatus && SecurityStatus.UefiSecureBootStatus; - LogFile.Log("Effective Secure Boot Status: " + EffectiveSecureBootStatus.ToString()); - - BootloaderSecurityQfuseStatus = SecurityStatus.SecureFfuEfuseStatus; - LogFile.Log("Bootloader Security Qfuse Status: " + BootloaderSecurityQfuseStatus.ToString()); - BootloaderSecurityAuthenticationStatus = SecurityStatus.AuthenticationStatus; - LogFile.Log("Bootloader Security Authentication Status: " + BootloaderSecurityAuthenticationStatus.ToString()); - BootloaderSecurityRdcStatus = SecurityStatus.RdcStatus; - LogFile.Log("Bootloader Security Rdc Status: " + BootloaderSecurityRdcStatus.ToString()); - EffectiveBootloaderSecurityStatus = SecurityStatus.SecureFfuEfuseStatus && !SecurityStatus.AuthenticationStatus && !SecurityStatus.RdcStatus; - LogFile.Log("Effective Bootloader Security Status: " + EffectiveBootloaderSecurityStatus.ToString()); - - NativeDebugStatus = !SecurityStatus.DebugStatus; - LogFile.Log("Native Debug Status: " + NativeDebugStatus.ToString()); - } - - byte[] CID = CurrentModel.ReadParam("CID"); - byte[] EMS = CurrentModel.ReadParam("EMS"); - if (CID != null && EMS != null) - { - UInt16 MID = (UInt16)((CID[0] << 8) + CID[1]); - UInt64 MemSize = (UInt64)(((UInt32)EMS[0] << 24) + ((UInt32)EMS[1] << 16) + ((UInt32)EMS[2] << 8) + EMS[3]) * 0x200; - double MemSizeDouble = (double)MemSize / 1024 / 1024 / 1024; - MemSizeDouble = (double)(int)(MemSizeDouble * 10) / 10; - string Manufacturer = null; - switch (MID) - { - case 0x0002: - case 0x0045: - Manufacturer = "SanDisk"; - break; - case 0x0011: - Manufacturer = "Toshiba"; - break; - case 0x0013: - Manufacturer = "Micron"; - break; - case 0x0015: - Manufacturer = "Samsung"; - break; - case 0x0090: - Manufacturer = "Hynix"; - break; - case 0x0070: - Manufacturer = "Kingston"; - break; - case 0x00EC: - Manufacturer = "GigaDevice"; - break; - } - eMMC = Manufacturer == null ? MemSizeDouble.ToString() + " GB" : Manufacturer + " " + MemSizeDouble.ToString() + " GB"; - - SamsungWarningVisible = MID == 0x0015; - } - else - { - eMMC = "Unknown"; - SamsungWarningVisible = true; - } - - int? chargecurrent = CurrentModel.ReadCurrentChargeCurrent(); - - if (chargecurrent.HasValue) - { - ChargingStatus = chargecurrent < 0 - ? CurrentModel.ReadCurrentChargeLevel() + "% - " + ((-1) * CurrentModel.ReadCurrentChargeCurrent()) + " mA (discharging)" - : CurrentModel.ReadCurrentChargeLevel() + "% - " + CurrentModel.ReadCurrentChargeCurrent() + " mA (charging)"; - - LogFile.Log("Charging status: " + ChargingStatus); - } - else - { - ChargingStatus = "Unknown"; - LogFile.Log("Charging status: " + ChargingStatus); - } - - PhoneInfo Info = CurrentModel.ReadPhoneInfo(true); - BootloaderDescription = Info.FlashAppProtocolVersionMajor < 2 ? "Lumia Bootloader Spec A" : "Lumia Bootloader Spec B"; - - LogFile.Log("Bootloader: " + BootloaderDescription); - - ProductCode = Info.ProductCode; - LogFile.Log("ProductCode: " + ProductCode); - - ProductType = Info.Type; - LogFile.Log("ProductType: " + ProductType); - - if (RootKeyHash == null) - { - LogFile.Log("Root Key Hash was null. Gathering information from an alternative source."); - - RootKeyHash = Info.RKH; - - if (RootKeyHash != null) - { - LogFile.Log("Root Key Hash: " + Converter.ConvertHexToString(RootKeyHash, " ")); - } - else - { - RootKeyHash = new byte[32]; - LogFile.Log("Root Key Hash: " + Converter.ConvertHexToString(RootKeyHash, " ")); - } - } - - if (PlatformName == null) - { - LogFile.Log("Platform Name was null. Gathering information from an alternative source."); - - PlatformName = Info.PlatformID; - LogFile.Log("Platform Name: " + PlatformName); - } - - if (SecurityStatus == null) - { - LogFile.Log("Security Status was null. Gathering information from an alternative source."); - - PlatformSecureBootStatus = Info.PlatformSecureBootEnabled; - LogFile.Log("Platform Secure Boot Status: " + PlatformSecureBootStatus.ToString()); - UefiSecureBootStatus = Info.UefiSecureBootEnabled; - LogFile.Log("Uefi Secure Boot Status: " + UefiSecureBootStatus.ToString()); - EffectiveSecureBootStatus = Info.PlatformSecureBootEnabled && Info.UefiSecureBootEnabled; - LogFile.Log("Effective Secure Boot Status: " + EffectiveSecureBootStatus.ToString()); - - BootloaderSecurityQfuseStatus = Info.SecureFfuEnabled; - LogFile.Log("Bootloader Security Qfuse Status: " + BootloaderSecurityQfuseStatus.ToString()); - BootloaderSecurityAuthenticationStatus = Info.Authenticated; - LogFile.Log("Bootloader Security Authentication Status: " + BootloaderSecurityAuthenticationStatus.ToString()); - BootloaderSecurityRdcStatus = Info.RdcPresent; - LogFile.Log("Bootloader Security Rdc Status: " + BootloaderSecurityRdcStatus.ToString()); - EffectiveBootloaderSecurityStatus = !Info.IsBootloaderSecure; - LogFile.Log("Effective Bootloader Security Status: " + EffectiveBootloaderSecurityStatus.ToString()); - - NativeDebugStatus = !Info.JtagDisabled; - LogFile.Log("Native Debug Status: " + NativeDebugStatus.ToString()); - } - } - catch - { - LogFile.Log("Reading status from Flash interface was aborted."); - } - DeviceInfoLoaded = true; - } - } - } - - private byte[] _PublicID = null; - public byte[] PublicID - { - get - { - return _PublicID; - } - set - { - _PublicID = value; - OnPropertyChanged(nameof(PublicID)); - } - } - - private byte[] _RootKeyHash = null; - public byte[] RootKeyHash - { - get - { - return _RootKeyHash; - } - set - { - _RootKeyHash = value; - OnPropertyChanged(nameof(RootKeyHash)); - } - } - - private string _PlatformName = null; - public string PlatformName - { - get - { - return _PlatformName; - } - set - { - _PlatformName = value; - OnPropertyChanged(nameof(PlatformName)); - } - } - - private string _ProductType = null; - public string ProductType - { - get - { - return _ProductType; - } - set - { - _ProductType = value; - OnPropertyChanged(nameof(ProductType)); - } - } - - private string _ProductCode = null; - public string ProductCode - { - get - { - return _ProductCode; - } - set - { - _ProductCode = value; - OnPropertyChanged(nameof(ProductCode)); - } - } - - private string _eMMC = null; - public string eMMC - { - get - { - return _eMMC; - } - set - { - _eMMC = value; - OnPropertyChanged(nameof(eMMC)); - } - } - - private string _BootloaderDescription = null; - public string BootloaderDescription - { - get - { - return _BootloaderDescription; - } - set - { - _BootloaderDescription = value; - OnPropertyChanged(nameof(BootloaderDescription)); - } - } - - private string _ChargingStatus = null; - public string ChargingStatus - { - get - { - return _ChargingStatus; - } - set - { - _ChargingStatus = value; - OnPropertyChanged(nameof(ChargingStatus)); - } - } - - private bool _SamsungWarningVisible = false; - public bool SamsungWarningVisible - { - get - { - return _SamsungWarningVisible; - } - set - { - _SamsungWarningVisible = value; - OnPropertyChanged(nameof(SamsungWarningVisible)); - } - } - - private bool? _PlatformSecureBootStatus = null; - public bool? PlatformSecureBootStatus - { - get - { - return _PlatformSecureBootStatus; - } - set - { - _PlatformSecureBootStatus = value; - OnPropertyChanged(nameof(PlatformSecureBootStatus)); - } - } - - private bool? _BootloaderSecurityQfuseStatus = null; - public bool? BootloaderSecurityQfuseStatus - { - get - { - return _BootloaderSecurityQfuseStatus; - } - set - { - _BootloaderSecurityQfuseStatus = value; - OnPropertyChanged(nameof(BootloaderSecurityQfuseStatus)); - } - } - - private bool? _BootloaderSecurityRdcStatus = null; - public bool? BootloaderSecurityRdcStatus - { - get - { - return _BootloaderSecurityRdcStatus; - } - set - { - _BootloaderSecurityRdcStatus = value; - OnPropertyChanged(nameof(BootloaderSecurityRdcStatus)); - } - } - - private bool? _BootloaderSecurityAuthenticationStatus = null; - public bool? BootloaderSecurityAuthenticationStatus - { - get - { - return _BootloaderSecurityAuthenticationStatus; - } - set - { - _BootloaderSecurityAuthenticationStatus = value; - OnPropertyChanged(nameof(BootloaderSecurityAuthenticationStatus)); - } - } - - private bool? _UefiSecureBootStatus = null; - public bool? UefiSecureBootStatus - { - get - { - return _UefiSecureBootStatus; - } - set - { - _UefiSecureBootStatus = value; - OnPropertyChanged(nameof(UefiSecureBootStatus)); - } - } - - private bool? _EffectiveSecureBootStatus = null; - public bool? EffectiveSecureBootStatus - { - get - { - return _EffectiveSecureBootStatus; - } - set - { - _EffectiveSecureBootStatus = value; - OnPropertyChanged(nameof(EffectiveSecureBootStatus)); - } - } - - private bool? _EffectiveBootloaderSecurityStatus = null; - public bool? EffectiveBootloaderSecurityStatus - { - get - { - return _EffectiveBootloaderSecurityStatus; - } - set - { - _EffectiveBootloaderSecurityStatus = value; - OnPropertyChanged(nameof(EffectiveBootloaderSecurityStatus)); - } - } - - private bool? _NativeDebugStatus = null; - public bool? NativeDebugStatus - { - get - { - return _NativeDebugStatus; - } - set - { - _NativeDebugStatus = value; - OnPropertyChanged(nameof(NativeDebugStatus)); - } - } - - #region Final Config - private bool? _FinalConfigSecureBootStatus = null; - public bool? FinalConfigSecureBootStatus - { - get - { - return _FinalConfigSecureBootStatus; - } - set - { - _FinalConfigSecureBootStatus = value; - OnPropertyChanged(nameof(FinalConfigSecureBootStatus)); - } - } - - private bool? _FinalConfigFfuVerifyStatus = null; - public bool? FinalConfigFfuVerifyStatus - { - get - { - return _FinalConfigFfuVerifyStatus; - } - set - { - _FinalConfigFfuVerifyStatus = value; - OnPropertyChanged(nameof(FinalConfigFfuVerifyStatus)); - } - } - - private bool? _FinalConfigJtagStatus = null; - public bool? FinalConfigJtagStatus - { - get - { - return _FinalConfigJtagStatus; - } - set - { - _FinalConfigJtagStatus = value; - OnPropertyChanged(nameof(FinalConfigJtagStatus)); - } - } - - private bool? _FinalConfigShkStatus = null; - public bool? FinalConfigShkStatus - { - get - { - return _FinalConfigShkStatus; - } - set - { - _FinalConfigShkStatus = value; - OnPropertyChanged(nameof(FinalConfigShkStatus)); - } - } - - private bool? _FinalConfigSimlockStatus = null; - public bool? FinalConfigSimlockStatus - { - get - { - return _FinalConfigSimlockStatus; - } - set - { - _FinalConfigSimlockStatus = value; - OnPropertyChanged(nameof(FinalConfigSimlockStatus)); - } - } - - private bool? _FinalConfigProductionDoneStatus = null; - public bool? FinalConfigProductionDoneStatus - { - get - { - return _FinalConfigProductionDoneStatus; - } - set - { - _FinalConfigProductionDoneStatus = value; - OnPropertyChanged(nameof(FinalConfigProductionDoneStatus)); - } - } - - private bool? _FinalConfigRkhStatus = null; - public bool? FinalConfigRkhStatus - { - get - { - return _FinalConfigRkhStatus; - } - set - { - _FinalConfigRkhStatus = value; - OnPropertyChanged(nameof(FinalConfigRkhStatus)); - } - } - - private bool? _FinalConfigPublicIdStatus = null; - public bool? FinalConfigPublicIdStatus - { - get - { - return _FinalConfigPublicIdStatus; - } - set - { - _FinalConfigPublicIdStatus = value; - OnPropertyChanged(nameof(FinalConfigPublicIdStatus)); - } - } - - private bool? _FinalConfigDakStatus = null; - public bool? FinalConfigDakStatus - { - get - { - return _FinalConfigDakStatus; - } - set - { - _FinalConfigDakStatus = value; - OnPropertyChanged(nameof(FinalConfigDakStatus)); - } - } - - private bool? _FinalConfigSecGenStatus = null; - public bool? FinalConfigSecGenStatus - { - get - { - return _FinalConfigSecGenStatus; - } - set - { - _FinalConfigSecGenStatus = value; - OnPropertyChanged(nameof(FinalConfigSecGenStatus)); - } - } - - private bool? _FinalConfigOemIdStatus = null; - public bool? FinalConfigOemIdStatus - { - get - { - return _FinalConfigOemIdStatus; - } - set - { - _FinalConfigOemIdStatus = value; - OnPropertyChanged(nameof(FinalConfigOemIdStatus)); - } - } - - private bool? _FinalConfigFastBootStatus = null; - public bool? FinalConfigFastBootStatus - { - get - { - return _FinalConfigFastBootStatus; - } - set - { - _FinalConfigFastBootStatus = value; - OnPropertyChanged(nameof(FinalConfigFastBootStatus)); - } - } - - private bool? _FinalConfigSpdmSecModeStatus = null; - public bool? FinalConfigSpdmSecModeStatus - { - get - { - return _FinalConfigSpdmSecModeStatus; - } - set - { - _FinalConfigSpdmSecModeStatus = value; - OnPropertyChanged(nameof(FinalConfigSpdmSecModeStatus)); - } - } - - private bool? _FinalConfigRpmWdogStatus = null; - public bool? FinalConfigRpmWdogStatus - { - get - { - return _FinalConfigRpmWdogStatus; - } - set - { - _FinalConfigRpmWdogStatus = value; - OnPropertyChanged(nameof(FinalConfigRpmWdogStatus)); - } - } - - private bool? _FinalConfigSsmStatus = null; - public bool? FinalConfigSsmStatus - { - get - { - return _FinalConfigSsmStatus; - } - set - { - _FinalConfigSsmStatus = value; - OnPropertyChanged(nameof(FinalConfigSsmStatus)); - } - } - #endregion - - internal void RebootTo(string Mode) - { - switch (Mode) - { - case "Normal": - RequestModeSwitch(PhoneInterfaces.Lumia_Normal); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "MassStorage": - RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + // Create this class on the UI thread, after the main-window of the application is initialized. + // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. + // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. + // Therefore the main window must be created before the ViewModel is created. + + internal class NokiaFlashViewModel : ContextViewModel + { + private readonly NokiaFlashModel CurrentModel; + private readonly Action RequestModeSwitch; + internal Action SwitchToGettingStarted; + private readonly object LockDeviceInfo = new(); + private bool DeviceInfoLoaded = false; + + internal NokiaFlashViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch, Action SwitchToGettingStarted) + : base() + { + this.CurrentModel = (NokiaFlashModel)CurrentModel; + this.RequestModeSwitch = RequestModeSwitch; + this.SwitchToGettingStarted = SwitchToGettingStarted; + } + + // Device info should be loaded only one time and only when the ViewModel is active + internal override void EvaluateViewState() + { + if (IsActive) + { + new Thread(() => StartLoadDeviceInfo()).Start(); + } + } + + private void StartLoadDeviceInfo() + { + lock (LockDeviceInfo) + { + if (!DeviceInfoLoaded) + { + try + { + //byte[] Imsi = CurrentModel.ExecuteJsonMethodAsBytes("ReadImsi", "Imsi"); // 9 bytes: 08 29 40 40 ... + //string BatteryLevel = CurrentModel.ExecuteJsonMethodAsString("ReadBatteryLevel", "BatteryLevel"); + //string SystemAsicVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSystemAsicVersion", "SystemAsicVersion"); // 8960 -> Chip SOC version + //string OperatorName = CurrentModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // 000-DK + //string ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 + //string AkVersion = CurrentModel.ExecuteJsonMethodAsString("ReadAkVersion", "AkVersion"); // 9200.10521 + //string BspVersion = CurrentModel.ExecuteJsonMethodAsString("ReadBspVersion", "BspVersion"); // 3051.40000 + //string ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 + //string SecurityMode = CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode"); // Restricted + //string SerialNumber = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", "SerialNumber"); // 356355051883955 = IMEI + //string SwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 + //string ModuleCode = CurrentModel.ExecuteJsonMethodAsString("ReadModuleCode", "ModuleCode"); // 0205137 + //byte[] PublicId = CurrentModel.ExecuteJsonMethodAsBytes("ReadPublicId", "PublicId"); // 0x14 bytes: a5 e5 ... + //string Psn = CurrentModel.ExecuteJsonMethodAsString("ReadPsn", "Psn"); // CEP737370 + //string HwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadHwVersion", "HWVersion"); // 6504 = 6.5.0.4 + //byte[] BtId = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... + //byte[] WlanMacAddress1 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes + //byte[] WlanMacAddress2 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress2"); // same + //byte[] WlanMacAddress3 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress3"); // same + //bool SimlockActive = CurrentModel.ExecuteJsonMethodAsBoolean("ReadSimlockActive", "SimLockActive"); // false + //string ServiceTag = CurrentModel.ExecuteJsonMethodAsString("ReadServiceTag", "ServiceTag"); // error + //byte[] RfChipsetVersion = CurrentModel.ExecuteJsonMethodAsBytes("ReadRfChipsetVersion", "RfChipsetVersion"); // error + //byte[] Meid = CurrentModel.ExecuteJsonMethodAsBytes("ReadMeid", "Meid"); // error + //string Test = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturingData", ""); -> This method is only possible in Label-mode. + + UefiSecurityStatusResponse SecurityStatus = CurrentModel.ReadSecurityStatus(); + + UInt32? FlagsResult = CurrentModel.ReadSecurityFlags(); + UInt32 SecurityFlags = 0; + if (FlagsResult != null) + { + SecurityFlags = (UInt32)CurrentModel.ReadSecurityFlags(); + LogFile.Log("Security flags: 0x" + SecurityFlags.ToString("X8")); + + FinalConfigDakStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Dak); + FinalConfigFastBootStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.FastBoot); + FinalConfigFfuVerifyStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.FfuVerify); + FinalConfigJtagStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Jtag); + FinalConfigOemIdStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.OemId); + FinalConfigProductionDoneStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.ProductionDone); + FinalConfigPublicIdStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.PublicId); + FinalConfigRkhStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Rkh); + FinalConfigRpmWdogStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.RpmWdog); + FinalConfigSecGenStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.SecGen); + FinalConfigSecureBootStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.SecureBoot); + FinalConfigShkStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Shk); + FinalConfigSimlockStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Simlock); + FinalConfigSpdmSecModeStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.SpdmSecMode); + FinalConfigSsmStatus = CurrentModel.ReadFuseStatus(NokiaFlashModel.Fuse.Ssm); + } + else + { + LogFile.Log("Security flags could not be read"); + } + + PlatformName = CurrentModel.ReadStringParam("DPI"); + LogFile.Log("Platform Name: " + PlatformName); + + // Some phones do not support the Terminal interface! (928 verizon) + // Instead read param RRKH to get the RKH. + PublicID = null; + byte[] RawPublicID = CurrentModel.ReadParam("PID"); + if (RawPublicID?.Length > 4) + { + PublicID = new byte[RawPublicID.Length - 4]; + Array.Copy(RawPublicID, 4, PublicID, 0, RawPublicID.Length - 4); + LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); + } + else + { + PublicID = new byte[20]; + LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); + } + RootKeyHash = CurrentModel.ReadParam("RRKH"); + if (RootKeyHash != null) + { + LogFile.Log("Root Key Hash: " + Converter.ConvertHexToString(RootKeyHash, " ")); + } + + if (SecurityStatus != null) + { + PlatformSecureBootStatus = SecurityStatus.PlatformSecureBootStatus; + LogFile.Log("Platform Secure Boot Status: " + PlatformSecureBootStatus.ToString()); + UefiSecureBootStatus = SecurityStatus.UefiSecureBootStatus; + LogFile.Log("Uefi Secure Boot Status: " + UefiSecureBootStatus.ToString()); + EffectiveSecureBootStatus = SecurityStatus.PlatformSecureBootStatus && SecurityStatus.UefiSecureBootStatus; + LogFile.Log("Effective Secure Boot Status: " + EffectiveSecureBootStatus.ToString()); + + BootloaderSecurityQfuseStatus = SecurityStatus.SecureFfuEfuseStatus; + LogFile.Log("Bootloader Security Qfuse Status: " + BootloaderSecurityQfuseStatus.ToString()); + BootloaderSecurityAuthenticationStatus = SecurityStatus.AuthenticationStatus; + LogFile.Log("Bootloader Security Authentication Status: " + BootloaderSecurityAuthenticationStatus.ToString()); + BootloaderSecurityRdcStatus = SecurityStatus.RdcStatus; + LogFile.Log("Bootloader Security Rdc Status: " + BootloaderSecurityRdcStatus.ToString()); + EffectiveBootloaderSecurityStatus = SecurityStatus.SecureFfuEfuseStatus && !SecurityStatus.AuthenticationStatus && !SecurityStatus.RdcStatus; + LogFile.Log("Effective Bootloader Security Status: " + EffectiveBootloaderSecurityStatus.ToString()); + + NativeDebugStatus = !SecurityStatus.DebugStatus; + LogFile.Log("Native Debug Status: " + NativeDebugStatus.ToString()); + } + + byte[] CID = CurrentModel.ReadParam("CID"); + byte[] EMS = CurrentModel.ReadParam("EMS"); + if (CID != null && EMS != null) + { + UInt16 MID = (UInt16)((CID[0] << 8) + CID[1]); + UInt64 MemSize = (UInt64)(((UInt32)EMS[0] << 24) + ((UInt32)EMS[1] << 16) + ((UInt32)EMS[2] << 8) + EMS[3]) * 0x200; + double MemSizeDouble = (double)MemSize / 1024 / 1024 / 1024; + MemSizeDouble = (double)(int)(MemSizeDouble * 10) / 10; + string Manufacturer = null; + switch (MID) + { + case 0x0002: + case 0x0045: + Manufacturer = "SanDisk"; + break; + case 0x0011: + Manufacturer = "Toshiba"; + break; + case 0x0013: + Manufacturer = "Micron"; + break; + case 0x0015: + Manufacturer = "Samsung"; + break; + case 0x0090: + Manufacturer = "Hynix"; + break; + case 0x0070: + Manufacturer = "Kingston"; + break; + case 0x00EC: + Manufacturer = "GigaDevice"; + break; + } + eMMC = Manufacturer == null ? MemSizeDouble.ToString() + " GB" : Manufacturer + " " + MemSizeDouble.ToString() + " GB"; + + SamsungWarningVisible = MID == 0x0015; + } + else + { + eMMC = "Unknown"; + SamsungWarningVisible = true; + } + + int? chargecurrent = CurrentModel.ReadCurrentChargeCurrent(); + + if (chargecurrent.HasValue) + { + ChargingStatus = chargecurrent < 0 + ? CurrentModel.ReadCurrentChargeLevel() + "% - " + ((-1) * CurrentModel.ReadCurrentChargeCurrent()) + " mA (discharging)" + : CurrentModel.ReadCurrentChargeLevel() + "% - " + CurrentModel.ReadCurrentChargeCurrent() + " mA (charging)"; + + LogFile.Log("Charging status: " + ChargingStatus); + } + else + { + ChargingStatus = "Unknown"; + LogFile.Log("Charging status: " + ChargingStatus); + } + + PhoneInfo Info = CurrentModel.ReadPhoneInfo(true); + BootloaderDescription = Info.FlashAppProtocolVersionMajor < 2 ? "Lumia Bootloader Spec A" : "Lumia Bootloader Spec B"; + + LogFile.Log("Bootloader: " + BootloaderDescription); + + ProductCode = Info.ProductCode; + LogFile.Log("ProductCode: " + ProductCode); + + ProductType = Info.Type; + LogFile.Log("ProductType: " + ProductType); + + if (RootKeyHash == null) + { + LogFile.Log("Root Key Hash was null. Gathering information from an alternative source."); + + RootKeyHash = Info.RKH; + + if (RootKeyHash != null) + { + LogFile.Log("Root Key Hash: " + Converter.ConvertHexToString(RootKeyHash, " ")); + } + else + { + RootKeyHash = new byte[32]; + LogFile.Log("Root Key Hash: " + Converter.ConvertHexToString(RootKeyHash, " ")); + } + } + + if (PlatformName == null) + { + LogFile.Log("Platform Name was null. Gathering information from an alternative source."); + + PlatformName = Info.PlatformID; + LogFile.Log("Platform Name: " + PlatformName); + } + + if (SecurityStatus == null) + { + LogFile.Log("Security Status was null. Gathering information from an alternative source."); + + PlatformSecureBootStatus = Info.PlatformSecureBootEnabled; + LogFile.Log("Platform Secure Boot Status: " + PlatformSecureBootStatus.ToString()); + UefiSecureBootStatus = Info.UefiSecureBootEnabled; + LogFile.Log("Uefi Secure Boot Status: " + UefiSecureBootStatus.ToString()); + EffectiveSecureBootStatus = Info.PlatformSecureBootEnabled && Info.UefiSecureBootEnabled; + LogFile.Log("Effective Secure Boot Status: " + EffectiveSecureBootStatus.ToString()); + + BootloaderSecurityQfuseStatus = Info.SecureFfuEnabled; + LogFile.Log("Bootloader Security Qfuse Status: " + BootloaderSecurityQfuseStatus.ToString()); + BootloaderSecurityAuthenticationStatus = Info.Authenticated; + LogFile.Log("Bootloader Security Authentication Status: " + BootloaderSecurityAuthenticationStatus.ToString()); + BootloaderSecurityRdcStatus = Info.RdcPresent; + LogFile.Log("Bootloader Security Rdc Status: " + BootloaderSecurityRdcStatus.ToString()); + EffectiveBootloaderSecurityStatus = !Info.IsBootloaderSecure; + LogFile.Log("Effective Bootloader Security Status: " + EffectiveBootloaderSecurityStatus.ToString()); + + NativeDebugStatus = !Info.JtagDisabled; + LogFile.Log("Native Debug Status: " + NativeDebugStatus.ToString()); + } + } + catch + { + LogFile.Log("Reading status from Flash interface was aborted."); + } + DeviceInfoLoaded = true; + } + } + } + + private byte[] _PublicID = null; + public byte[] PublicID + { + get + { + return _PublicID; + } + set + { + _PublicID = value; + OnPropertyChanged(nameof(PublicID)); + } + } + + private byte[] _RootKeyHash = null; + public byte[] RootKeyHash + { + get + { + return _RootKeyHash; + } + set + { + _RootKeyHash = value; + OnPropertyChanged(nameof(RootKeyHash)); + } + } + + private string _PlatformName = null; + public string PlatformName + { + get + { + return _PlatformName; + } + set + { + _PlatformName = value; + OnPropertyChanged(nameof(PlatformName)); + } + } + + private string _ProductType = null; + public string ProductType + { + get + { + return _ProductType; + } + set + { + _ProductType = value; + OnPropertyChanged(nameof(ProductType)); + } + } + + private string _ProductCode = null; + public string ProductCode + { + get + { + return _ProductCode; + } + set + { + _ProductCode = value; + OnPropertyChanged(nameof(ProductCode)); + } + } + + private string _eMMC = null; + public string eMMC + { + get + { + return _eMMC; + } + set + { + _eMMC = value; + OnPropertyChanged(nameof(eMMC)); + } + } + + private string _BootloaderDescription = null; + public string BootloaderDescription + { + get + { + return _BootloaderDescription; + } + set + { + _BootloaderDescription = value; + OnPropertyChanged(nameof(BootloaderDescription)); + } + } + + private string _ChargingStatus = null; + public string ChargingStatus + { + get + { + return _ChargingStatus; + } + set + { + _ChargingStatus = value; + OnPropertyChanged(nameof(ChargingStatus)); + } + } + + private bool _SamsungWarningVisible = false; + public bool SamsungWarningVisible + { + get + { + return _SamsungWarningVisible; + } + set + { + _SamsungWarningVisible = value; + OnPropertyChanged(nameof(SamsungWarningVisible)); + } + } + + private bool? _PlatformSecureBootStatus = null; + public bool? PlatformSecureBootStatus + { + get + { + return _PlatformSecureBootStatus; + } + set + { + _PlatformSecureBootStatus = value; + OnPropertyChanged(nameof(PlatformSecureBootStatus)); + } + } + + private bool? _BootloaderSecurityQfuseStatus = null; + public bool? BootloaderSecurityQfuseStatus + { + get + { + return _BootloaderSecurityQfuseStatus; + } + set + { + _BootloaderSecurityQfuseStatus = value; + OnPropertyChanged(nameof(BootloaderSecurityQfuseStatus)); + } + } + + private bool? _BootloaderSecurityRdcStatus = null; + public bool? BootloaderSecurityRdcStatus + { + get + { + return _BootloaderSecurityRdcStatus; + } + set + { + _BootloaderSecurityRdcStatus = value; + OnPropertyChanged(nameof(BootloaderSecurityRdcStatus)); + } + } + + private bool? _BootloaderSecurityAuthenticationStatus = null; + public bool? BootloaderSecurityAuthenticationStatus + { + get + { + return _BootloaderSecurityAuthenticationStatus; + } + set + { + _BootloaderSecurityAuthenticationStatus = value; + OnPropertyChanged(nameof(BootloaderSecurityAuthenticationStatus)); + } + } + + private bool? _UefiSecureBootStatus = null; + public bool? UefiSecureBootStatus + { + get + { + return _UefiSecureBootStatus; + } + set + { + _UefiSecureBootStatus = value; + OnPropertyChanged(nameof(UefiSecureBootStatus)); + } + } + + private bool? _EffectiveSecureBootStatus = null; + public bool? EffectiveSecureBootStatus + { + get + { + return _EffectiveSecureBootStatus; + } + set + { + _EffectiveSecureBootStatus = value; + OnPropertyChanged(nameof(EffectiveSecureBootStatus)); + } + } + + private bool? _EffectiveBootloaderSecurityStatus = null; + public bool? EffectiveBootloaderSecurityStatus + { + get + { + return _EffectiveBootloaderSecurityStatus; + } + set + { + _EffectiveBootloaderSecurityStatus = value; + OnPropertyChanged(nameof(EffectiveBootloaderSecurityStatus)); + } + } + + private bool? _NativeDebugStatus = null; + public bool? NativeDebugStatus + { + get + { + return _NativeDebugStatus; + } + set + { + _NativeDebugStatus = value; + OnPropertyChanged(nameof(NativeDebugStatus)); + } + } + + #region Final Config + private bool? _FinalConfigSecureBootStatus = null; + public bool? FinalConfigSecureBootStatus + { + get + { + return _FinalConfigSecureBootStatus; + } + set + { + _FinalConfigSecureBootStatus = value; + OnPropertyChanged(nameof(FinalConfigSecureBootStatus)); + } + } + + private bool? _FinalConfigFfuVerifyStatus = null; + public bool? FinalConfigFfuVerifyStatus + { + get + { + return _FinalConfigFfuVerifyStatus; + } + set + { + _FinalConfigFfuVerifyStatus = value; + OnPropertyChanged(nameof(FinalConfigFfuVerifyStatus)); + } + } + + private bool? _FinalConfigJtagStatus = null; + public bool? FinalConfigJtagStatus + { + get + { + return _FinalConfigJtagStatus; + } + set + { + _FinalConfigJtagStatus = value; + OnPropertyChanged(nameof(FinalConfigJtagStatus)); + } + } + + private bool? _FinalConfigShkStatus = null; + public bool? FinalConfigShkStatus + { + get + { + return _FinalConfigShkStatus; + } + set + { + _FinalConfigShkStatus = value; + OnPropertyChanged(nameof(FinalConfigShkStatus)); + } + } + + private bool? _FinalConfigSimlockStatus = null; + public bool? FinalConfigSimlockStatus + { + get + { + return _FinalConfigSimlockStatus; + } + set + { + _FinalConfigSimlockStatus = value; + OnPropertyChanged(nameof(FinalConfigSimlockStatus)); + } + } + + private bool? _FinalConfigProductionDoneStatus = null; + public bool? FinalConfigProductionDoneStatus + { + get + { + return _FinalConfigProductionDoneStatus; + } + set + { + _FinalConfigProductionDoneStatus = value; + OnPropertyChanged(nameof(FinalConfigProductionDoneStatus)); + } + } + + private bool? _FinalConfigRkhStatus = null; + public bool? FinalConfigRkhStatus + { + get + { + return _FinalConfigRkhStatus; + } + set + { + _FinalConfigRkhStatus = value; + OnPropertyChanged(nameof(FinalConfigRkhStatus)); + } + } + + private bool? _FinalConfigPublicIdStatus = null; + public bool? FinalConfigPublicIdStatus + { + get + { + return _FinalConfigPublicIdStatus; + } + set + { + _FinalConfigPublicIdStatus = value; + OnPropertyChanged(nameof(FinalConfigPublicIdStatus)); + } + } + + private bool? _FinalConfigDakStatus = null; + public bool? FinalConfigDakStatus + { + get + { + return _FinalConfigDakStatus; + } + set + { + _FinalConfigDakStatus = value; + OnPropertyChanged(nameof(FinalConfigDakStatus)); + } + } + + private bool? _FinalConfigSecGenStatus = null; + public bool? FinalConfigSecGenStatus + { + get + { + return _FinalConfigSecGenStatus; + } + set + { + _FinalConfigSecGenStatus = value; + OnPropertyChanged(nameof(FinalConfigSecGenStatus)); + } + } + + private bool? _FinalConfigOemIdStatus = null; + public bool? FinalConfigOemIdStatus + { + get + { + return _FinalConfigOemIdStatus; + } + set + { + _FinalConfigOemIdStatus = value; + OnPropertyChanged(nameof(FinalConfigOemIdStatus)); + } + } + + private bool? _FinalConfigFastBootStatus = null; + public bool? FinalConfigFastBootStatus + { + get + { + return _FinalConfigFastBootStatus; + } + set + { + _FinalConfigFastBootStatus = value; + OnPropertyChanged(nameof(FinalConfigFastBootStatus)); + } + } + + private bool? _FinalConfigSpdmSecModeStatus = null; + public bool? FinalConfigSpdmSecModeStatus + { + get + { + return _FinalConfigSpdmSecModeStatus; + } + set + { + _FinalConfigSpdmSecModeStatus = value; + OnPropertyChanged(nameof(FinalConfigSpdmSecModeStatus)); + } + } + + private bool? _FinalConfigRpmWdogStatus = null; + public bool? FinalConfigRpmWdogStatus + { + get + { + return _FinalConfigRpmWdogStatus; + } + set + { + _FinalConfigRpmWdogStatus = value; + OnPropertyChanged(nameof(FinalConfigRpmWdogStatus)); + } + } + + private bool? _FinalConfigSsmStatus = null; + public bool? FinalConfigSsmStatus + { + get + { + return _FinalConfigSsmStatus; + } + set + { + _FinalConfigSsmStatus = value; + OnPropertyChanged(nameof(FinalConfigSsmStatus)); + } + } + #endregion + + internal void RebootTo(string Mode) + { + switch (Mode) + { + case "Normal": + RequestModeSwitch(PhoneInterfaces.Lumia_Normal); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "MassStorage": + RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/NokiaLabelViewModel.cs b/WPinternals/ViewModels/NokiaLabelViewModel.cs similarity index 97% rename from ViewModels/NokiaLabelViewModel.cs rename to WPinternals/ViewModels/NokiaLabelViewModel.cs index a385c29..4c136b4 100644 --- a/ViewModels/NokiaLabelViewModel.cs +++ b/WPinternals/ViewModels/NokiaLabelViewModel.cs @@ -1,297 +1,297 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Threading; - -namespace WPinternals -{ - // Create this class on the UI thread, after the main-window of the application is initialized. - // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. - // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. - // Therefore the main window must be created before the ViewModel is created. - - internal class NokiaLabelViewModel : ContextViewModel - { - private readonly NokiaPhoneModel CurrentModel; - private readonly Action RequestModeSwitch; - - internal NokiaLabelViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) - : base() - { - this.RequestModeSwitch = RequestModeSwitch; - - this.CurrentModel = CurrentModel; - - new Thread(() => StartLoadDeviceInfo()).Start(); - } - - private void StartLoadDeviceInfo() - { - //byte[] Imsi = CurrentModel.ExecuteJsonMethodAsBytes("ReadImsi", "Imsi"); // 9 bytes: 08 29 40 40 ... - //string BatteryLevel = CurrentModel.ExecuteJsonMethodAsString("ReadBatteryLevel", "BatteryLevel"); - //string SystemAsicVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSystemAsicVersion", "SystemAsicVersion"); // 8960 -> Chip SOC version - //string OperatorName = CurrentModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // 000-DK - //string ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 - //string AkVersion = CurrentModel.ExecuteJsonMethodAsString("ReadAkVersion", "AkVersion"); // 9200.10521 - //string BspVersion = CurrentModel.ExecuteJsonMethodAsString("ReadBspVersion", "BspVersion"); // 3051.40000 - //string ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 - //string SecurityMode = CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode"); // Restricted - //string SerialNumber = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", "SerialNumber"); // 356355051883955 = IMEI - //string SwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 - //string ModuleCode = CurrentModel.ExecuteJsonMethodAsString("ReadModuleCode", "ModuleCode"); // 0205137 - //byte[] PublicId = CurrentModel.ExecuteJsonMethodAsBytes("ReadPublicId", "PublicId"); // 0x14 bytes: a5 e5 ... - //string Psn = CurrentModel.ExecuteJsonMethodAsString("ReadPsn", "Psn"); // CEP737370 - //string HwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadHwVersion", "HWVersion"); // 6504 = 6.5.0.4 - //byte[] BtId = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... - //byte[] WlanMacAddress1 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes - //byte[] WlanMacAddress2 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress2"); // same - //byte[] WlanMacAddress3 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress3"); // same - //bool SimlockActive = CurrentModel.ExecuteJsonMethodAsBoolean("ReadSimlockActive", "SimLockActive"); // false - //string Version = CurrentModel.ExecuteJsonMethodAsString("GetVersion", "HelloString"); // Resultvars: HelloString Version BuildDate BuildType - //string ServiceTag = CurrentModel.ExecuteJsonMethodAsString("ReadServiceTag", "ServiceTag"); // error - //byte[] RfChipsetVersion = CurrentModel.ExecuteJsonMethodAsBytes("ReadRfChipsetVersion", "RfChipsetVersion"); // error - //byte[] Meid = CurrentModel.ExecuteJsonMethodAsBytes("ReadMeid", "Meid"); // error - //string Test = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturingData", ""); -> This method is only possible in Label-mode. - - byte[] AsskMask = new byte[0x10] { 1, 0, 16, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 }; - byte[] Challenge = new byte[0x88]; - Dictionary Params = new(); - Params.Add("AsskMask", AsskMask); - Params.Add("Challenge", Challenge); - Params.Add("AsicIndex", 0); - byte[] TerminalResponseBytes = CurrentModel.ExecuteJsonMethodAsBytes("TerminalChallenge", Params, "TerminalResponse"); - if (TerminalResponseBytes != null) - { - TerminalResponse TerminalResponse = Terminal.Parse(TerminalResponseBytes, 0); - if (TerminalResponse != null) - { - PublicID = TerminalResponse.PublicId; - LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); - RootKeyHash = TerminalResponse.RootKeyHash; - LogFile.Log("RootKeyHash: " + Converter.ConvertHexToString(RootKeyHash, " ")); - } - } - - ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 - LogFile.Log("Manufacturer Model Name: " + ManufacturerModelName); - ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 - LogFile.Log("Product Code: " + ProductCode); - Firmware = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 - LogFile.Log("Firmware: " + Firmware); - - IMEI = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", "SerialNumber"); // IMEI - LogFile.Log("IMEI: " + IMEI); - BluetoothMac = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... - LogFile.Log("Bluetooth MAC: " + Converter.ConvertHexToString(BluetoothMac, " ")); - WlanMac = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes - LogFile.Log("WLAN MAC: " + Converter.ConvertHexToString(WlanMac, " ")); - - IsBootloaderSecurityEnabled = (bool)CurrentModel.ExecuteJsonMethodAsBoolean("ReadProductionDoneState", "ProductionDone"); - LogFile.Log("Bootloader Security: " + ((bool)IsBootloaderSecurityEnabled ? "Enabled" : "Disabled")); - - Params = new Dictionary - { - { "ID", 3534 }, - { "NVData", new byte[] { 0 } } - }; - CurrentModel.ExecuteJsonMethod("WriteNVData", Params); // Error: 150 - - Params = new Dictionary - { - { "ID", 3534 } - }; - byte[] NV3534 = CurrentModel.ExecuteJsonMethodAsBytes("ReadNVData", Params, "NVData"); // Error: value not written - } - - private string _ProductCode = null; - public string ProductCode - { - get - { - return _ProductCode; - } - set - { - _ProductCode = value; - OnPropertyChanged(nameof(ProductCode)); - } - } - - private string _ManufacturerModelName = null; - public string ManufacturerModelName - { - get - { - return _ManufacturerModelName; - } - set - { - _ManufacturerModelName = value; - OnPropertyChanged(nameof(ManufacturerModelName)); - } - } - - private string _Operator = null; - public string Operator - { - get - { - return _Operator; - } - set - { - _Operator = value; - OnPropertyChanged(nameof(Operator)); - } - } - - private string _Firmware = null; - public string Firmware - { - get - { - return _Firmware; - } - set - { - _Firmware = value; - OnPropertyChanged(nameof(Firmware)); - } - } - - private string _IMEI = null; - public string IMEI - { - get - { - return _IMEI; - } - set - { - _IMEI = value; - OnPropertyChanged(nameof(IMEI)); - } - } - - private byte[] _PublicID = null; - public byte[] PublicID - { - get - { - return _PublicID; - } - set - { - _PublicID = value; - OnPropertyChanged(nameof(PublicID)); - } - } - - private byte[] _RootKeyHash = null; - public byte[] RootKeyHash - { - get - { - return _RootKeyHash; - } - set - { - _RootKeyHash = value; - OnPropertyChanged(nameof(RootKeyHash)); - } - } - - private byte[] _WlanMac = null; - public byte[] WlanMac - { - get - { - return _WlanMac; - } - set - { - _WlanMac = value; - OnPropertyChanged(nameof(WlanMac)); - } - } - - private byte[] _BluetoothMac = null; - public byte[] BluetoothMac - { - get - { - return _BluetoothMac; - } - set - { - _BluetoothMac = value; - OnPropertyChanged(nameof(BluetoothMac)); - } - } - - private bool? _IsBootloaderSecurityEnabled = null; - public bool? IsBootloaderSecurityEnabled - { - get - { - return _IsBootloaderSecurityEnabled; - } - set - { - _IsBootloaderSecurityEnabled = value; - OnPropertyChanged(nameof(IsBootloaderSecurityEnabled)); - } - } - - private bool? _IsSimLocked = null; - public bool? IsSimLocked - { - get - { - return _IsSimLocked; - } - set - { - _IsSimLocked = value; - OnPropertyChanged(nameof(IsSimLocked)); - } - } - - public void RebootTo(string Mode) - { - switch (Mode) - { - case "Flash": - RequestModeSwitch(PhoneInterfaces.Lumia_Flash); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "MassStorage": - RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Threading; + +namespace WPinternals +{ + // Create this class on the UI thread, after the main-window of the application is initialized. + // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. + // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. + // Therefore the main window must be created before the ViewModel is created. + + internal class NokiaLabelViewModel : ContextViewModel + { + private readonly NokiaPhoneModel CurrentModel; + private readonly Action RequestModeSwitch; + + internal NokiaLabelViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) + : base() + { + this.RequestModeSwitch = RequestModeSwitch; + + this.CurrentModel = CurrentModel; + + new Thread(() => StartLoadDeviceInfo()).Start(); + } + + private void StartLoadDeviceInfo() + { + //byte[] Imsi = CurrentModel.ExecuteJsonMethodAsBytes("ReadImsi", "Imsi"); // 9 bytes: 08 29 40 40 ... + //string BatteryLevel = CurrentModel.ExecuteJsonMethodAsString("ReadBatteryLevel", "BatteryLevel"); + //string SystemAsicVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSystemAsicVersion", "SystemAsicVersion"); // 8960 -> Chip SOC version + //string OperatorName = CurrentModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // 000-DK + //string ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 + //string AkVersion = CurrentModel.ExecuteJsonMethodAsString("ReadAkVersion", "AkVersion"); // 9200.10521 + //string BspVersion = CurrentModel.ExecuteJsonMethodAsString("ReadBspVersion", "BspVersion"); // 3051.40000 + //string ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 + //string SecurityMode = CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode"); // Restricted + //string SerialNumber = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", "SerialNumber"); // 356355051883955 = IMEI + //string SwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 + //string ModuleCode = CurrentModel.ExecuteJsonMethodAsString("ReadModuleCode", "ModuleCode"); // 0205137 + //byte[] PublicId = CurrentModel.ExecuteJsonMethodAsBytes("ReadPublicId", "PublicId"); // 0x14 bytes: a5 e5 ... + //string Psn = CurrentModel.ExecuteJsonMethodAsString("ReadPsn", "Psn"); // CEP737370 + //string HwVersion = CurrentModel.ExecuteJsonMethodAsString("ReadHwVersion", "HWVersion"); // 6504 = 6.5.0.4 + //byte[] BtId = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... + //byte[] WlanMacAddress1 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes + //byte[] WlanMacAddress2 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress2"); // same + //byte[] WlanMacAddress3 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress3"); // same + //bool SimlockActive = CurrentModel.ExecuteJsonMethodAsBoolean("ReadSimlockActive", "SimLockActive"); // false + //string Version = CurrentModel.ExecuteJsonMethodAsString("GetVersion", "HelloString"); // Resultvars: HelloString Version BuildDate BuildType + //string ServiceTag = CurrentModel.ExecuteJsonMethodAsString("ReadServiceTag", "ServiceTag"); // error + //byte[] RfChipsetVersion = CurrentModel.ExecuteJsonMethodAsBytes("ReadRfChipsetVersion", "RfChipsetVersion"); // error + //byte[] Meid = CurrentModel.ExecuteJsonMethodAsBytes("ReadMeid", "Meid"); // error + //string Test = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturingData", ""); -> This method is only possible in Label-mode. + + byte[] AsskMask = new byte[0x10] { 1, 0, 16, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 }; + byte[] Challenge = new byte[0x88]; + Dictionary Params = new(); + Params.Add("AsskMask", AsskMask); + Params.Add("Challenge", Challenge); + Params.Add("AsicIndex", 0); + byte[] TerminalResponseBytes = CurrentModel.ExecuteJsonMethodAsBytes("TerminalChallenge", Params, "TerminalResponse"); + if (TerminalResponseBytes != null) + { + TerminalResponse TerminalResponse = Terminal.Parse(TerminalResponseBytes, 0); + if (TerminalResponse != null) + { + PublicID = TerminalResponse.PublicId; + LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); + RootKeyHash = TerminalResponse.RootKeyHash; + LogFile.Log("RootKeyHash: " + Converter.ConvertHexToString(RootKeyHash, " ")); + } + } + + ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 + LogFile.Log("Manufacturer Model Name: " + ManufacturerModelName); + ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 + LogFile.Log("Product Code: " + ProductCode); + Firmware = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 + LogFile.Log("Firmware: " + Firmware); + + IMEI = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", "SerialNumber"); // IMEI + LogFile.Log("IMEI: " + IMEI); + BluetoothMac = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... + LogFile.Log("Bluetooth MAC: " + Converter.ConvertHexToString(BluetoothMac, " ")); + WlanMac = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes + LogFile.Log("WLAN MAC: " + Converter.ConvertHexToString(WlanMac, " ")); + + IsBootloaderSecurityEnabled = (bool)CurrentModel.ExecuteJsonMethodAsBoolean("ReadProductionDoneState", "ProductionDone"); + LogFile.Log("Bootloader Security: " + ((bool)IsBootloaderSecurityEnabled ? "Enabled" : "Disabled")); + + Params = new Dictionary + { + { "ID", 3534 }, + { "NVData", new byte[] { 0 } } + }; + CurrentModel.ExecuteJsonMethod("WriteNVData", Params); // Error: 150 + + Params = new Dictionary + { + { "ID", 3534 } + }; + byte[] NV3534 = CurrentModel.ExecuteJsonMethodAsBytes("ReadNVData", Params, "NVData"); // Error: value not written + } + + private string _ProductCode = null; + public string ProductCode + { + get + { + return _ProductCode; + } + set + { + _ProductCode = value; + OnPropertyChanged(nameof(ProductCode)); + } + } + + private string _ManufacturerModelName = null; + public string ManufacturerModelName + { + get + { + return _ManufacturerModelName; + } + set + { + _ManufacturerModelName = value; + OnPropertyChanged(nameof(ManufacturerModelName)); + } + } + + private string _Operator = null; + public string Operator + { + get + { + return _Operator; + } + set + { + _Operator = value; + OnPropertyChanged(nameof(Operator)); + } + } + + private string _Firmware = null; + public string Firmware + { + get + { + return _Firmware; + } + set + { + _Firmware = value; + OnPropertyChanged(nameof(Firmware)); + } + } + + private string _IMEI = null; + public string IMEI + { + get + { + return _IMEI; + } + set + { + _IMEI = value; + OnPropertyChanged(nameof(IMEI)); + } + } + + private byte[] _PublicID = null; + public byte[] PublicID + { + get + { + return _PublicID; + } + set + { + _PublicID = value; + OnPropertyChanged(nameof(PublicID)); + } + } + + private byte[] _RootKeyHash = null; + public byte[] RootKeyHash + { + get + { + return _RootKeyHash; + } + set + { + _RootKeyHash = value; + OnPropertyChanged(nameof(RootKeyHash)); + } + } + + private byte[] _WlanMac = null; + public byte[] WlanMac + { + get + { + return _WlanMac; + } + set + { + _WlanMac = value; + OnPropertyChanged(nameof(WlanMac)); + } + } + + private byte[] _BluetoothMac = null; + public byte[] BluetoothMac + { + get + { + return _BluetoothMac; + } + set + { + _BluetoothMac = value; + OnPropertyChanged(nameof(BluetoothMac)); + } + } + + private bool? _IsBootloaderSecurityEnabled = null; + public bool? IsBootloaderSecurityEnabled + { + get + { + return _IsBootloaderSecurityEnabled; + } + set + { + _IsBootloaderSecurityEnabled = value; + OnPropertyChanged(nameof(IsBootloaderSecurityEnabled)); + } + } + + private bool? _IsSimLocked = null; + public bool? IsSimLocked + { + get + { + return _IsSimLocked; + } + set + { + _IsSimLocked = value; + OnPropertyChanged(nameof(IsSimLocked)); + } + } + + public void RebootTo(string Mode) + { + switch (Mode) + { + case "Flash": + RequestModeSwitch(PhoneInterfaces.Lumia_Flash); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "MassStorage": + RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/NokiaMassStorageViewModel.cs b/WPinternals/ViewModels/NokiaMassStorageViewModel.cs similarity index 97% rename from ViewModels/NokiaMassStorageViewModel.cs rename to WPinternals/ViewModels/NokiaMassStorageViewModel.cs index afd1275..9e3405a 100644 --- a/ViewModels/NokiaMassStorageViewModel.cs +++ b/WPinternals/ViewModels/NokiaMassStorageViewModel.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -namespace WPinternals -{ - internal class NokiaMassStorageViewModel : ContextViewModel - { - private readonly MassStorage CurrentModel; - - internal NokiaMassStorageViewModel(MassStorage CurrentModel) - : base() - { - this.CurrentModel = CurrentModel; - Drive = CurrentModel.Drive; - } - public string Drive { get; } = null; - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace WPinternals +{ + internal class NokiaMassStorageViewModel : ContextViewModel + { + private readonly MassStorage CurrentModel; + + internal NokiaMassStorageViewModel(MassStorage CurrentModel) + : base() + { + this.CurrentModel = CurrentModel; + Drive = CurrentModel.Drive; + } + public string Drive { get; } = null; + } +} diff --git a/ViewModels/NokiaModeBootloaderViewModel.cs b/WPinternals/ViewModels/NokiaModeBootloaderViewModel.cs similarity index 100% rename from ViewModels/NokiaModeBootloaderViewModel.cs rename to WPinternals/ViewModels/NokiaModeBootloaderViewModel.cs diff --git a/ViewModels/NokiaModeFlashViewModel.cs b/WPinternals/ViewModels/NokiaModeFlashViewModel.cs similarity index 97% rename from ViewModels/NokiaModeFlashViewModel.cs rename to WPinternals/ViewModels/NokiaModeFlashViewModel.cs index 50b2d02..f8b0ac6 100644 --- a/ViewModels/NokiaModeFlashViewModel.cs +++ b/WPinternals/ViewModels/NokiaModeFlashViewModel.cs @@ -1,120 +1,120 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - internal class NokiaModeFlashViewModel : ContextViewModel - { - private readonly NokiaFlashModel CurrentModel; - private readonly Action RequestModeSwitch; - private readonly object LockDeviceInfo = new(); - private bool DeviceInfoLoaded = false; - - internal NokiaModeFlashViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) - : base() - { - this.CurrentModel = (NokiaFlashModel)CurrentModel; - this.RequestModeSwitch = RequestModeSwitch; - } - - internal override void EvaluateViewState() - { - if (IsActive) - { - new Thread(() => StartLoadDeviceInfo()).Start(); - } - } - - private bool? _EffectiveBootloaderSecurityStatus = null; - public bool? EffectiveBootloaderSecurityStatus - { - get - { - return _EffectiveBootloaderSecurityStatus; - } - set - { - _EffectiveBootloaderSecurityStatus = value; - OnPropertyChanged(nameof(EffectiveBootloaderSecurityStatus)); - } - } - - internal void StartLoadDeviceInfo() - { - lock (LockDeviceInfo) - { - if (!DeviceInfoLoaded) - { - try - { - PhoneInfo Info = CurrentModel.ReadPhoneInfo(); - - if (Info.FlashAppProtocolVersionMajor < 2) - { - UefiSecurityStatusResponse SecurityStatus = CurrentModel.ReadSecurityStatus(); - - EffectiveBootloaderSecurityStatus = SecurityStatus != null - ? SecurityStatus.SecureFfuEfuseStatus && !SecurityStatus.AuthenticationStatus && !SecurityStatus.RdcStatus - : !Info.IsBootloaderSecure; - } - else - { - EffectiveBootloaderSecurityStatus = Info.UefiSecureBootEnabled; - } - - LogFile.Log("Effective Bootloader Security Status: " + EffectiveBootloaderSecurityStatus.ToString()); - } - catch - { - LogFile.Log("Reading status from Flash interface was aborted."); - } - DeviceInfoLoaded = true; - } - } - } - - internal void RebootTo(string Mode) - { - switch (Mode) - { - case "Normal": - RequestModeSwitch(PhoneInterfaces.Lumia_Normal); - break; - case "Flash": - RequestModeSwitch(PhoneInterfaces.Lumia_Flash); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "MassStorage": - RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); - break; - case "Shutdown": - RequestModeSwitch(null); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + internal class NokiaModeFlashViewModel : ContextViewModel + { + private readonly NokiaFlashModel CurrentModel; + private readonly Action RequestModeSwitch; + private readonly object LockDeviceInfo = new(); + private bool DeviceInfoLoaded = false; + + internal NokiaModeFlashViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) + : base() + { + this.CurrentModel = (NokiaFlashModel)CurrentModel; + this.RequestModeSwitch = RequestModeSwitch; + } + + internal override void EvaluateViewState() + { + if (IsActive) + { + new Thread(() => StartLoadDeviceInfo()).Start(); + } + } + + private bool? _EffectiveBootloaderSecurityStatus = null; + public bool? EffectiveBootloaderSecurityStatus + { + get + { + return _EffectiveBootloaderSecurityStatus; + } + set + { + _EffectiveBootloaderSecurityStatus = value; + OnPropertyChanged(nameof(EffectiveBootloaderSecurityStatus)); + } + } + + internal void StartLoadDeviceInfo() + { + lock (LockDeviceInfo) + { + if (!DeviceInfoLoaded) + { + try + { + PhoneInfo Info = CurrentModel.ReadPhoneInfo(); + + if (Info.FlashAppProtocolVersionMajor < 2) + { + UefiSecurityStatusResponse SecurityStatus = CurrentModel.ReadSecurityStatus(); + + EffectiveBootloaderSecurityStatus = SecurityStatus != null + ? SecurityStatus.SecureFfuEfuseStatus && !SecurityStatus.AuthenticationStatus && !SecurityStatus.RdcStatus + : !Info.IsBootloaderSecure; + } + else + { + EffectiveBootloaderSecurityStatus = Info.UefiSecureBootEnabled; + } + + LogFile.Log("Effective Bootloader Security Status: " + EffectiveBootloaderSecurityStatus.ToString()); + } + catch + { + LogFile.Log("Reading status from Flash interface was aborted."); + } + DeviceInfoLoaded = true; + } + } + } + + internal void RebootTo(string Mode) + { + switch (Mode) + { + case "Normal": + RequestModeSwitch(PhoneInterfaces.Lumia_Normal); + break; + case "Flash": + RequestModeSwitch(PhoneInterfaces.Lumia_Flash); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "MassStorage": + RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); + break; + case "Shutdown": + RequestModeSwitch(null); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/NokiaModeLabelViewModel.cs b/WPinternals/ViewModels/NokiaModeLabelViewModel.cs similarity index 97% rename from ViewModels/NokiaModeLabelViewModel.cs rename to WPinternals/ViewModels/NokiaModeLabelViewModel.cs index 3f467f1..8ad5834 100644 --- a/ViewModels/NokiaModeLabelViewModel.cs +++ b/WPinternals/ViewModels/NokiaModeLabelViewModel.cs @@ -1,58 +1,58 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class NokiaModeLabelViewModel : ContextViewModel - { - private readonly NokiaPhoneModel CurrentModel; - private readonly Action RequestModeSwitch; - - internal NokiaModeLabelViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) - : base() - { - this.CurrentModel = CurrentModel; - this.RequestModeSwitch = RequestModeSwitch; - } - - public void RebootTo(string Mode) - { - switch (Mode) - { - case "Normal": - RequestModeSwitch(PhoneInterfaces.Lumia_Normal); - break; - case "Flash": - RequestModeSwitch(PhoneInterfaces.Lumia_Flash); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "MassStorage": - RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class NokiaModeLabelViewModel : ContextViewModel + { + private readonly NokiaPhoneModel CurrentModel; + private readonly Action RequestModeSwitch; + + internal NokiaModeLabelViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) + : base() + { + this.CurrentModel = CurrentModel; + this.RequestModeSwitch = RequestModeSwitch; + } + + public void RebootTo(string Mode) + { + switch (Mode) + { + case "Normal": + RequestModeSwitch(PhoneInterfaces.Lumia_Normal); + break; + case "Flash": + RequestModeSwitch(PhoneInterfaces.Lumia_Flash); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "MassStorage": + RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/NokiaModeMassStorageViewModel.cs b/WPinternals/ViewModels/NokiaModeMassStorageViewModel.cs similarity index 97% rename from ViewModels/NokiaModeMassStorageViewModel.cs rename to WPinternals/ViewModels/NokiaModeMassStorageViewModel.cs index 99c210a..b1f316b 100644 --- a/ViewModels/NokiaModeMassStorageViewModel.cs +++ b/WPinternals/ViewModels/NokiaModeMassStorageViewModel.cs @@ -1,80 +1,80 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class NokiaModeMassStorageViewModel : ContextViewModel - { - private readonly MassStorage CurrentModel; - private readonly Action RequestModeSwitch; - - internal NokiaModeMassStorageViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) - : base() - { - this.CurrentModel = (MassStorage)CurrentModel; - this.RequestModeSwitch = RequestModeSwitch; - } - - private bool _SupportsReboot = false; - public bool SupportsReboot - { - get - { - return _SupportsReboot; - } - set - { - _SupportsReboot = value; - OnPropertyChanged(nameof(SupportsReboot)); - } - } - - internal override void EvaluateViewState() - { - if (IsActive) - { - SupportsReboot = CurrentModel.DoesDeviceSupportReboot(); - } - } - - public void RebootTo(string Mode) - { - switch (Mode) - { - case "Normal": - RequestModeSwitch(PhoneInterfaces.Lumia_Normal); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "Flash": - RequestModeSwitch(PhoneInterfaces.Lumia_Flash); - break; - case "Shutdown": - RequestModeSwitch(null); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class NokiaModeMassStorageViewModel : ContextViewModel + { + private readonly MassStorage CurrentModel; + private readonly Action RequestModeSwitch; + + internal NokiaModeMassStorageViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) + : base() + { + this.CurrentModel = (MassStorage)CurrentModel; + this.RequestModeSwitch = RequestModeSwitch; + } + + private bool _SupportsReboot = false; + public bool SupportsReboot + { + get + { + return _SupportsReboot; + } + set + { + _SupportsReboot = value; + OnPropertyChanged(nameof(SupportsReboot)); + } + } + + internal override void EvaluateViewState() + { + if (IsActive) + { + SupportsReboot = CurrentModel.DoesDeviceSupportReboot(); + } + } + + public void RebootTo(string Mode) + { + switch (Mode) + { + case "Normal": + RequestModeSwitch(PhoneInterfaces.Lumia_Normal); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "Flash": + RequestModeSwitch(PhoneInterfaces.Lumia_Flash); + break; + case "Shutdown": + RequestModeSwitch(null); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/NokiaModeNormalViewModel.cs b/WPinternals/ViewModels/NokiaModeNormalViewModel.cs similarity index 97% rename from ViewModels/NokiaModeNormalViewModel.cs rename to WPinternals/ViewModels/NokiaModeNormalViewModel.cs index fb513e7..d3d88cb 100644 --- a/ViewModels/NokiaModeNormalViewModel.cs +++ b/WPinternals/ViewModels/NokiaModeNormalViewModel.cs @@ -1,55 +1,55 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace WPinternals -{ - internal class NokiaModeNormalViewModel : ContextViewModel - { - private readonly NokiaPhoneModel CurrentModel; - private readonly Action RequestModeSwitch; - - internal NokiaModeNormalViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) - : base() - { - this.CurrentModel = CurrentModel; - this.RequestModeSwitch = RequestModeSwitch; - } - - public void RebootTo(string Mode) - { - switch (Mode) - { - case "Flash": - RequestModeSwitch(PhoneInterfaces.Lumia_Flash); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "MassStorage": - RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace WPinternals +{ + internal class NokiaModeNormalViewModel : ContextViewModel + { + private readonly NokiaPhoneModel CurrentModel; + private readonly Action RequestModeSwitch; + + internal NokiaModeNormalViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) + : base() + { + this.CurrentModel = CurrentModel; + this.RequestModeSwitch = RequestModeSwitch; + } + + public void RebootTo(string Mode) + { + switch (Mode) + { + case "Flash": + RequestModeSwitch(PhoneInterfaces.Lumia_Flash); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "MassStorage": + RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/NokiaNormalViewModel.cs b/WPinternals/ViewModels/NokiaNormalViewModel.cs similarity index 97% rename from ViewModels/NokiaNormalViewModel.cs rename to WPinternals/ViewModels/NokiaNormalViewModel.cs index cd84dc7..45064bf 100644 --- a/ViewModels/NokiaNormalViewModel.cs +++ b/WPinternals/ViewModels/NokiaNormalViewModel.cs @@ -1,395 +1,395 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - // Create this class on the UI thread, after the main-window of the application is initialized. - // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. - // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. - // Therefore the main window must be created before the ViewModel is created. - - internal class NokiaNormalViewModel : ContextViewModel - { - private readonly NokiaPhoneModel CurrentModel; - private readonly Action RequestModeSwitch; - - internal NokiaNormalViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) - : base() - { - this.CurrentModel = CurrentModel; - this.RequestModeSwitch = RequestModeSwitch; - - new Thread(() => StartLoadDeviceInfo()).Start(); - } - - private void StartLoadDeviceInfo() - { - try - { - Operator = CurrentModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // 000-NL - LogFile.Log("Operator: " + Operator); - ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 - LogFile.Log("Manufacturer Model Name: " + ManufacturerModelName); - ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 - LogFile.Log("Product Code: " + ProductCode); - Firmware = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 - LogFile.Log("Firmware: " + Firmware); - HWID = CurrentModel.ExecuteJsonMethodAsString("ReadHwVersion", "HWVersion"); // 1002 - LogFile.Log("HWID: " + HWID); - - IMEI = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", new System.Collections.Generic.Dictionary() { { "SubscriptionId", 0 } }, "SerialNumber"); // IMEI - string IMEI2 = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", new System.Collections.Generic.Dictionary() { { "SubscriptionId", 1 } }, "SerialNumber"); // IMEI 2 - - if (!string.IsNullOrEmpty(IMEI2)) - { - IMEI += "\n" + IMEI2; - } - - LogFile.Log("IMEI: " + IMEI); - PublicID = CurrentModel.ExecuteJsonMethodAsBytes("ReadPublicId", "PublicId"); // 0x14 bytes: a5 e5 ... - LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); - BluetoothMac = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... - LogFile.Log("Bluetooth MAC: " + Converter.ConvertHexToString(BluetoothMac, " ")); - WlanMac1 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes - LogFile.Log("WLAN MAC 1: " + Converter.ConvertHexToString(WlanMac1, " ")); - WlanMac2 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress2"); // 6 bytes - LogFile.Log("WLAN MAC 2: " + Converter.ConvertHexToString(WlanMac2, " ")); - WlanMac3 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress3"); // 6 bytes - LogFile.Log("WLAN MAC 3: " + Converter.ConvertHexToString(WlanMac3, " ")); - WlanMac4 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress4"); // 6 bytes - LogFile.Log("WLAN MAC 4: " + Converter.ConvertHexToString(WlanMac4, " ")); - - bool? ProductionDone = CurrentModel.ExecuteJsonMethodAsBoolean("ReadProductionDoneState", "ProductionDone"); - IsBootloaderSecurityEnabled = ProductionDone == null - ? CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode").Contains("Restricted", StringComparison.OrdinalIgnoreCase) - : CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode").Contains("Restricted", StringComparison.OrdinalIgnoreCase) && (bool)CurrentModel.ExecuteJsonMethodAsBoolean("ReadProductionDoneState", "ProductionDone"); - - LogFile.Log("Bootloader Security: " + ((bool)IsBootloaderSecurityEnabled ? "Enabled" : "Disabled")); - IsSimLocked = CurrentModel.ExecuteJsonMethodAsBoolean("ReadSimlockActive", "SimLockActive"); - LogFile.Log("Simlock: " + ((bool)IsSimLocked ? "Active" : "Unlocked")); - - string BootPolicy = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "BootPolicy"); - string Db = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Db"); - string Dbx = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Dbx"); - string Kek = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Kek"); - string Pk = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Pk"); - - this.BootPolicy = BootPolicy; - LogFile.Log("Boot policy: " + BootPolicy); - this.Db = Db; - LogFile.Log("DB: " + Db); - this.Dbx = Dbx; - LogFile.Log("DBX: " + Dbx); - this.Kek = Kek; - LogFile.Log("KEK: " + Kek); - this.Pk = Pk; - LogFile.Log("PK: " + Pk); - } - catch { } - } - - private string _ProductCode = null; - public string ProductCode - { - get - { - return _ProductCode; - } - set - { - _ProductCode = value; - OnPropertyChanged(nameof(ProductCode)); - } - } - - private string _ManufacturerModelName = null; - public string ManufacturerModelName - { - get - { - return _ManufacturerModelName; - } - set - { - _ManufacturerModelName = value; - OnPropertyChanged(nameof(ManufacturerModelName)); - } - } - - private string _Operator = null; - public string Operator - { - get - { - return _Operator; - } - set - { - _Operator = value; - OnPropertyChanged(nameof(Operator)); - } - } - - private string _Firmware = null; - public string Firmware - { - get - { - return _Firmware; - } - set - { - _Firmware = value; - OnPropertyChanged(nameof(Firmware)); - } - } - - private string _HWID = null; - public string HWID - { - get - { - return _HWID; - } - set - { - _HWID = value; - OnPropertyChanged(nameof(HWID)); - } - } - - private string _IMEI = null; - public string IMEI - { - get - { - return _IMEI; - } - set - { - _IMEI = value; - OnPropertyChanged(nameof(IMEI)); - } - } - - private string _BootPolicy = null; - public string BootPolicy - { - get - { - return _BootPolicy; - } - set - { - _BootPolicy = value; - OnPropertyChanged(nameof(BootPolicy)); - } - } - - private string _Db = null; - public string Db - { - get - { - return _Db; - } - set - { - _Db = value; - OnPropertyChanged(nameof(Db)); - } - } - - private string _Dbx = null; - public string Dbx - { - get - { - return _Dbx; - } - set - { - _Dbx = value; - OnPropertyChanged(nameof(Dbx)); - } - } - - private string _Kek = null; - public string Kek - { - get - { - return _Kek; - } - set - { - _Kek = value; - OnPropertyChanged(nameof(Kek)); - } - } - - private string _Pk = null; - public string Pk - { - get - { - return _Pk; - } - set - { - _Pk = value; - OnPropertyChanged(nameof(Pk)); - } - } - - private byte[] _PublicID = null; - public byte[] PublicID - { - get - { - return _PublicID; - } - set - { - _PublicID = value; - OnPropertyChanged(nameof(PublicID)); - } - } - - private byte[] _WlanMac1 = null; - public byte[] WlanMac1 - { - get - { - return _WlanMac1; - } - set - { - _WlanMac1 = value; - OnPropertyChanged(nameof(WlanMac1)); - } - } - - private byte[] _WlanMac2 = null; - public byte[] WlanMac2 - { - get - { - return _WlanMac2; - } - set - { - _WlanMac2 = value; - OnPropertyChanged(nameof(WlanMac2)); - } - } - - private byte[] _WlanMac3 = null; - public byte[] WlanMac3 - { - get - { - return _WlanMac3; - } - set - { - _WlanMac3 = value; - OnPropertyChanged(nameof(WlanMac3)); - } - } - - private byte[] _WlanMac4 = null; - public byte[] WlanMac4 - { - get - { - return _WlanMac4; - } - set - { - _WlanMac4 = value; - OnPropertyChanged(nameof(WlanMac4)); - } - } - - private byte[] _BluetoothMac = null; - public byte[] BluetoothMac - { - get - { - return _BluetoothMac; - } - set - { - _BluetoothMac = value; - OnPropertyChanged(nameof(BluetoothMac)); - } - } - - private bool? _IsBootloaderSecurityEnabled = null; - public bool? IsBootloaderSecurityEnabled - { - get - { - return _IsBootloaderSecurityEnabled; - } - set - { - _IsBootloaderSecurityEnabled = value; - OnPropertyChanged(nameof(IsBootloaderSecurityEnabled)); - } - } - - private bool? _IsSimLocked = null; - public bool? IsSimLocked - { - get - { - return _IsSimLocked; - } - set - { - _IsSimLocked = value; - OnPropertyChanged(nameof(IsSimLocked)); - } - } - - public void RebootTo(string Mode) - { - switch (Mode) - { - case "Flash": - RequestModeSwitch(PhoneInterfaces.Lumia_Flash); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "MassStorage": - RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + // Create this class on the UI thread, after the main-window of the application is initialized. + // It is necessary to create the object on the UI thread, because notification events to the View need to be fired on that thread. + // The Model for this ViewModel communicates over USB and for that it uses the hWnd of the main window. + // Therefore the main window must be created before the ViewModel is created. + + internal class NokiaNormalViewModel : ContextViewModel + { + private readonly NokiaPhoneModel CurrentModel; + private readonly Action RequestModeSwitch; + + internal NokiaNormalViewModel(NokiaPhoneModel CurrentModel, Action RequestModeSwitch) + : base() + { + this.CurrentModel = CurrentModel; + this.RequestModeSwitch = RequestModeSwitch; + + new Thread(() => StartLoadDeviceInfo()).Start(); + } + + private void StartLoadDeviceInfo() + { + try + { + Operator = CurrentModel.ExecuteJsonMethodAsString("ReadOperatorName", "OperatorName"); // 000-NL + LogFile.Log("Operator: " + Operator); + ManufacturerModelName = CurrentModel.ExecuteJsonMethodAsString("ReadManufacturerModelName", "ManufacturerModelName"); // RM-821_eu_denmark_251 + LogFile.Log("Manufacturer Model Name: " + ManufacturerModelName); + ProductCode = CurrentModel.ExecuteJsonMethodAsString("ReadProductCode", "ProductCode"); // 059Q9D7 + LogFile.Log("Product Code: " + ProductCode); + Firmware = CurrentModel.ExecuteJsonMethodAsString("ReadSwVersion", "SwVersion"); // 3051.40000.1349.0007 + LogFile.Log("Firmware: " + Firmware); + HWID = CurrentModel.ExecuteJsonMethodAsString("ReadHwVersion", "HWVersion"); // 1002 + LogFile.Log("HWID: " + HWID); + + IMEI = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", new System.Collections.Generic.Dictionary() { { "SubscriptionId", 0 } }, "SerialNumber"); // IMEI + string IMEI2 = CurrentModel.ExecuteJsonMethodAsString("ReadSerialNumber", new System.Collections.Generic.Dictionary() { { "SubscriptionId", 1 } }, "SerialNumber"); // IMEI 2 + + if (!string.IsNullOrEmpty(IMEI2)) + { + IMEI += "\n" + IMEI2; + } + + LogFile.Log("IMEI: " + IMEI); + PublicID = CurrentModel.ExecuteJsonMethodAsBytes("ReadPublicId", "PublicId"); // 0x14 bytes: a5 e5 ... + LogFile.Log("Public ID: " + Converter.ConvertHexToString(PublicID, " ")); + BluetoothMac = CurrentModel.ExecuteJsonMethodAsBytes("ReadBtId", "BtId"); // 6 bytes: bc c6 ... + LogFile.Log("Bluetooth MAC: " + Converter.ConvertHexToString(BluetoothMac, " ")); + WlanMac1 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress1"); // 6 bytes + LogFile.Log("WLAN MAC 1: " + Converter.ConvertHexToString(WlanMac1, " ")); + WlanMac2 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress2"); // 6 bytes + LogFile.Log("WLAN MAC 2: " + Converter.ConvertHexToString(WlanMac2, " ")); + WlanMac3 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress3"); // 6 bytes + LogFile.Log("WLAN MAC 3: " + Converter.ConvertHexToString(WlanMac3, " ")); + WlanMac4 = CurrentModel.ExecuteJsonMethodAsBytes("ReadWlanMacAddress", "WlanMacAddress4"); // 6 bytes + LogFile.Log("WLAN MAC 4: " + Converter.ConvertHexToString(WlanMac4, " ")); + + bool? ProductionDone = CurrentModel.ExecuteJsonMethodAsBoolean("ReadProductionDoneState", "ProductionDone"); + IsBootloaderSecurityEnabled = ProductionDone == null + ? CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode").Contains("Restricted", StringComparison.OrdinalIgnoreCase) + : CurrentModel.ExecuteJsonMethodAsString("GetSecurityMode", "SecMode").Contains("Restricted", StringComparison.OrdinalIgnoreCase) && (bool)CurrentModel.ExecuteJsonMethodAsBoolean("ReadProductionDoneState", "ProductionDone"); + + LogFile.Log("Bootloader Security: " + ((bool)IsBootloaderSecurityEnabled ? "Enabled" : "Disabled")); + IsSimLocked = CurrentModel.ExecuteJsonMethodAsBoolean("ReadSimlockActive", "SimLockActive"); + LogFile.Log("Simlock: " + ((bool)IsSimLocked ? "Active" : "Unlocked")); + + string BootPolicy = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "BootPolicy"); + string Db = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Db"); + string Dbx = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Dbx"); + string Kek = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Kek"); + string Pk = CurrentModel.ExecuteJsonMethodAsString("GetUefiCertificateStatus", "Pk"); + + this.BootPolicy = BootPolicy; + LogFile.Log("Boot policy: " + BootPolicy); + this.Db = Db; + LogFile.Log("DB: " + Db); + this.Dbx = Dbx; + LogFile.Log("DBX: " + Dbx); + this.Kek = Kek; + LogFile.Log("KEK: " + Kek); + this.Pk = Pk; + LogFile.Log("PK: " + Pk); + } + catch { } + } + + private string _ProductCode = null; + public string ProductCode + { + get + { + return _ProductCode; + } + set + { + _ProductCode = value; + OnPropertyChanged(nameof(ProductCode)); + } + } + + private string _ManufacturerModelName = null; + public string ManufacturerModelName + { + get + { + return _ManufacturerModelName; + } + set + { + _ManufacturerModelName = value; + OnPropertyChanged(nameof(ManufacturerModelName)); + } + } + + private string _Operator = null; + public string Operator + { + get + { + return _Operator; + } + set + { + _Operator = value; + OnPropertyChanged(nameof(Operator)); + } + } + + private string _Firmware = null; + public string Firmware + { + get + { + return _Firmware; + } + set + { + _Firmware = value; + OnPropertyChanged(nameof(Firmware)); + } + } + + private string _HWID = null; + public string HWID + { + get + { + return _HWID; + } + set + { + _HWID = value; + OnPropertyChanged(nameof(HWID)); + } + } + + private string _IMEI = null; + public string IMEI + { + get + { + return _IMEI; + } + set + { + _IMEI = value; + OnPropertyChanged(nameof(IMEI)); + } + } + + private string _BootPolicy = null; + public string BootPolicy + { + get + { + return _BootPolicy; + } + set + { + _BootPolicy = value; + OnPropertyChanged(nameof(BootPolicy)); + } + } + + private string _Db = null; + public string Db + { + get + { + return _Db; + } + set + { + _Db = value; + OnPropertyChanged(nameof(Db)); + } + } + + private string _Dbx = null; + public string Dbx + { + get + { + return _Dbx; + } + set + { + _Dbx = value; + OnPropertyChanged(nameof(Dbx)); + } + } + + private string _Kek = null; + public string Kek + { + get + { + return _Kek; + } + set + { + _Kek = value; + OnPropertyChanged(nameof(Kek)); + } + } + + private string _Pk = null; + public string Pk + { + get + { + return _Pk; + } + set + { + _Pk = value; + OnPropertyChanged(nameof(Pk)); + } + } + + private byte[] _PublicID = null; + public byte[] PublicID + { + get + { + return _PublicID; + } + set + { + _PublicID = value; + OnPropertyChanged(nameof(PublicID)); + } + } + + private byte[] _WlanMac1 = null; + public byte[] WlanMac1 + { + get + { + return _WlanMac1; + } + set + { + _WlanMac1 = value; + OnPropertyChanged(nameof(WlanMac1)); + } + } + + private byte[] _WlanMac2 = null; + public byte[] WlanMac2 + { + get + { + return _WlanMac2; + } + set + { + _WlanMac2 = value; + OnPropertyChanged(nameof(WlanMac2)); + } + } + + private byte[] _WlanMac3 = null; + public byte[] WlanMac3 + { + get + { + return _WlanMac3; + } + set + { + _WlanMac3 = value; + OnPropertyChanged(nameof(WlanMac3)); + } + } + + private byte[] _WlanMac4 = null; + public byte[] WlanMac4 + { + get + { + return _WlanMac4; + } + set + { + _WlanMac4 = value; + OnPropertyChanged(nameof(WlanMac4)); + } + } + + private byte[] _BluetoothMac = null; + public byte[] BluetoothMac + { + get + { + return _BluetoothMac; + } + set + { + _BluetoothMac = value; + OnPropertyChanged(nameof(BluetoothMac)); + } + } + + private bool? _IsBootloaderSecurityEnabled = null; + public bool? IsBootloaderSecurityEnabled + { + get + { + return _IsBootloaderSecurityEnabled; + } + set + { + _IsBootloaderSecurityEnabled = value; + OnPropertyChanged(nameof(IsBootloaderSecurityEnabled)); + } + } + + private bool? _IsSimLocked = null; + public bool? IsSimLocked + { + get + { + return _IsSimLocked; + } + set + { + _IsSimLocked = value; + OnPropertyChanged(nameof(IsSimLocked)); + } + } + + public void RebootTo(string Mode) + { + switch (Mode) + { + case "Flash": + RequestModeSwitch(PhoneInterfaces.Lumia_Flash); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "MassStorage": + RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/NotImplementedViewModel.cs b/WPinternals/ViewModels/NotImplementedViewModel.cs similarity index 97% rename from ViewModels/NotImplementedViewModel.cs rename to WPinternals/ViewModels/NotImplementedViewModel.cs index 2b2ae6e..7aa08c9 100644 --- a/ViewModels/NotImplementedViewModel.cs +++ b/WPinternals/ViewModels/NotImplementedViewModel.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -namespace WPinternals -{ - internal class NotImplementedViewModel : ContextViewModel - { - internal string Message { get; set; } - - internal NotImplementedViewModel(MainViewModel Main) - : base(Main) - { - Message = "Not implemented yet..."; - } - - internal NotImplementedViewModel(MainViewModel Main, string Message) - : base(Main) - { - this.Message = Message; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace WPinternals +{ + internal class NotImplementedViewModel : ContextViewModel + { + internal string Message { get; set; } + + internal NotImplementedViewModel(MainViewModel Main) + : base(Main) + { + Message = "Not implemented yet..."; + } + + internal NotImplementedViewModel(MainViewModel Main, string Message) + : base(Main) + { + this.Message = Message; + } + } +} diff --git a/ViewModels/PhoneNotifierViewModel.cs b/WPinternals/ViewModels/PhoneNotifierViewModel.cs similarity index 98% rename from ViewModels/PhoneNotifierViewModel.cs rename to WPinternals/ViewModels/PhoneNotifierViewModel.cs index 09d94a1..7f35498 100644 --- a/ViewModels/PhoneNotifierViewModel.cs +++ b/WPinternals/ViewModels/PhoneNotifierViewModel.cs @@ -1,497 +1,497 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using MadWizard.WinUSBNet; -using System; -using System.Diagnostics.Eventing.Reader; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace WPinternals -{ - internal delegate void NewDeviceArrivedEvent(ArrivalEventArgs Args); - internal delegate void DeviceRemovedEvent(); - - internal class PhoneNotifierViewModel - { - private USBNotifier LumiaOldCombiNotifier; - private USBNotifier LumiaNewCombiNotifier; - private USBNotifier LumiaNormalNotifier; - private USBNotifier LumiaFlashNotifier; - private USBNotifier StorageNotifier; - private USBNotifier ComPortNotifier; - private USBNotifier LumiaEmergencyNotifier; - private USBNotifier LumiaLabelNotifier; - private USBNotifier HidInterfaceNotifier; - - public PhoneInterfaces? CurrentInterface = null; - private PhoneInterfaces? LastInterface = null; - public IDisposable CurrentModel = null; - - public event NewDeviceArrivedEvent NewDeviceArrived = delegate { }; - public event DeviceRemovedEvent DeviceRemoved = delegate { }; - - private Guid OldCombiInterfaceGuid = new("{0FD3B15C-D457-45d8-A779-C2B2C9F9D0FD}"); - private Guid NewCombiInterfaceGuid = new("{7eaff726-34cc-4204-b09d-f95471b873cf}"); - - private Guid MassStorageInterfaceGuid = new("{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}"); - private Guid ComPortInterfaceGuid = new("{86E0D1E0-8089-11D0-9CE4-08003E301F73}"); - private Guid HidInterfaceGuid = new("{4D1E55B2-F16F-11CF-88CB-001111000030}"); - - private Guid LumiaNormalInterfaceGuid = new("{08324F9C-B621-435C-859B-AE4652481B7C}"); - private Guid LumiaLabelInterfaceGuid = new("{F4FE0C27-7304-4ED7-AAB5-130893B84B6F}"); - private Guid LumiaFlashInterfaceGuid = new("{9e3bd5f7-9690-4fcc-8810-3e2650cd6ecc}"); - private Guid LumiaEmergencyInterfaceGuid = new("{71DE994D-8B7C-43DB-A27E-2AE7CD579A0C}"); - - private readonly object ModelLock = new(); - - private readonly EventWaitHandle NewInterfaceWaitHandle = new(false, EventResetMode.AutoReset); - - private EventLogWatcher LogWatcher; - - private string Qcom9006DevicePath; - - internal void Start() - { - LumiaOldCombiNotifier = new USBNotifier(OldCombiInterfaceGuid); - LumiaOldCombiNotifier.Arrival += LumiaNotifier_Arrival; - LumiaOldCombiNotifier.Removal += LumiaNotifier_Removal; - - LumiaNewCombiNotifier = new USBNotifier(NewCombiInterfaceGuid); - LumiaNewCombiNotifier.Arrival += LumiaNotifier_Arrival; - LumiaNewCombiNotifier.Removal += LumiaNotifier_Removal; - - LumiaNormalNotifier = new USBNotifier(LumiaNormalInterfaceGuid); - LumiaNormalNotifier.Arrival += LumiaNotifier_Arrival; - LumiaNormalNotifier.Removal += LumiaNotifier_Removal; - - LumiaFlashNotifier = new USBNotifier(LumiaFlashInterfaceGuid); - LumiaFlashNotifier.Arrival += LumiaNotifier_Arrival; - LumiaFlashNotifier.Removal += LumiaNotifier_Removal; - - StorageNotifier = new USBNotifier(MassStorageInterfaceGuid); - StorageNotifier.Arrival += LumiaNotifier_Arrival; - StorageNotifier.Removal += LumiaNotifier_Removal; - - ComPortNotifier = new USBNotifier(ComPortInterfaceGuid); - ComPortNotifier.Arrival += LumiaNotifier_Arrival; - ComPortNotifier.Removal += LumiaNotifier_Removal; - - LumiaEmergencyNotifier = new USBNotifier(LumiaEmergencyInterfaceGuid); - LumiaEmergencyNotifier.Arrival += LumiaNotifier_Arrival; - LumiaEmergencyNotifier.Removal += LumiaNotifier_Removal; - - LumiaLabelNotifier = new USBNotifier(LumiaLabelInterfaceGuid); - LumiaLabelNotifier.Arrival += LumiaNotifier_Arrival; - LumiaLabelNotifier.Removal += LumiaNotifier_Removal; - - HidInterfaceNotifier = new USBNotifier(HidInterfaceGuid); - HidInterfaceNotifier.Arrival += LumiaNotifier_Arrival; - HidInterfaceNotifier.Removal += LumiaNotifier_Removal; - - try - { - EventLogQuery LogQuery = new("Microsoft-Windows-Kernel-PnP/Configuration", PathType.LogName, "*[System[(EventID = 411)]]"); - LogWatcher = new EventLogWatcher(LogQuery); - LogWatcher.EventRecordWritten += PnPEventWritten; - LogWatcher.Enabled = true; - App.IsPnPEventLogMissing = false; - } - catch { } - } - - private void PnPEventWritten(Object obj, EventRecordWrittenEventArgs arg) - { - string Description = arg.EventRecord.FormatDescription(); - if (Description.IndexOf("VID_045E&PID_9006", 0, StringComparison.OrdinalIgnoreCase) >= 0) - { - LogFile.Log("Event " + arg.EventRecord.Id.ToString() + ": " + Description, LogType.FileOnly); - LogFile.Log("Phone switched to Mass Storage mode, but the driver on the PC did not start correctly", LogType.FileAndConsole); - CurrentInterface = PhoneInterfaces.Lumia_BadMassStorage; - CurrentModel = null; - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - } - } - - internal void Stop() - { - LumiaOldCombiNotifier.Dispose(); - LumiaNewCombiNotifier.Dispose(); - LumiaNormalNotifier.Dispose(); - LumiaFlashNotifier.Dispose(); - StorageNotifier.Dispose(); - ComPortNotifier.Dispose(); - LumiaEmergencyNotifier.Dispose(); - HidInterfaceNotifier.Dispose(); - LogWatcher.Dispose(); - } - - internal async Task WaitForNextNodeChange() - { - // Node change events are on all USBnotifiers, so we just pick one - await LumiaEmergencyNotifier.WaitForNextNodeChange(); - } - - internal void NotifyArrival() - { - if (CurrentInterface != null) - { - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - } - } - - private void LumiaNotifier_Arrival(object sender, USBEvent e) - { - try - { - if (e.DevicePath.Contains("VID_0421&", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_045E&", StringComparison.OrdinalIgnoreCase)) - { - if (e.DevicePath.Contains("&PID_0660&MI_04", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("&PID_0713&MI_04", StringComparison.OrdinalIgnoreCase) || // for Spec B - e.DevicePath.Contains("&PID_0A01&MI_04", StringComparison.OrdinalIgnoreCase)) // for Spec B (650) - { - CurrentInterface = PhoneInterfaces.Lumia_Label; - CurrentModel = new NokiaPhoneModel(e.DevicePath); - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Label", LogType.FileAndConsole); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - } - else if (e.DevicePath.Contains("&PID_0661", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("&PID_06FC", StringComparison.OrdinalIgnoreCase) || // VID_0421&PID_06FC is for Lumia 930 - e.DevicePath.Contains("&PID_0A00", StringComparison.OrdinalIgnoreCase)) // vid_045e & pid_0a00 & mi_03 = Lumia 950 XL normal mode - { - if (((USBNotifier)sender).Guid == OldCombiInterfaceGuid) - { - NewInterfaceWaitHandle.Reset(); - if (USBDevice.GetDevices(NewCombiInterfaceGuid).Length > 0) - { - return; - } - else - { - // Old combi-interface was detected, but new combi-interface was not detected. - // This could mean 2 things: - // - It is a WP80 phone, which has only this old combi-interface to talk to. - // - It is a WP81 / W10M phone, which has an unresponsive old combi-interface and we need to wait for the new combi-interface to arrive. - // We will wait maximum 1 sec for the new interface. If it doesn't arrive we will start talking on this old interface. - // We will start a new thread, because if this thread is blocked, no new devices will arrive. - string DevicePath = e.DevicePath; - ThreadPool.QueueUserWorkItem(s => - { - if (!NewInterfaceWaitHandle.WaitOne(1000)) - { - // Waithandle not set. - // So new interface did not arrive. - // So we assume we need to talk to this old interface. - - CurrentInterface = PhoneInterfaces.Lumia_Normal; - CurrentModel = new NokiaPhoneModel(DevicePath); - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Normal", LogType.FileAndConsole); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - } - }); - } - } - else - { - NewInterfaceWaitHandle.Set(); - - CurrentInterface = PhoneInterfaces.Lumia_Normal; - CurrentModel = new NokiaPhoneModel(e.DevicePath); - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Normal", LogType.FileAndConsole); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - } - } - else if (e.DevicePath.Contains("&PID_066E", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("&PID_0714", StringComparison.OrdinalIgnoreCase) || // VID_0421&PID_0714 is for Lumia 930 - e.DevicePath.Contains("&PID_0A02", StringComparison.OrdinalIgnoreCase) || // VID_045E&PID_0A02 is for Lumia 950 - e.DevicePath.Contains("&PID_05EE", StringComparison.OrdinalIgnoreCase)) // VID_0421&PID_05EE is for early RX100 - { - CurrentModel = new NokiaFlashModel(e.DevicePath); - ((NokiaFlashModel)CurrentModel).InterfaceChanged += InterfaceChanged; - - FlashAppType type = FlashAppType.FlashApp; - try - { - type = ((NokiaFlashModel)CurrentModel).GetFlashAppType(); - LogFile.Log("Flash App Type: " + type.ToString(), LogType.FileOnly); - } - catch - { - LogFile.Log("Flash App Type could not be determined, assuming " + type.ToString(), LogType.FileOnly); - } - - switch (type) - { - case FlashAppType.BootManager: - { - CurrentInterface = PhoneInterfaces.Lumia_Bootloader; - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Bootloader", LogType.FileAndConsole); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - break; - } - case FlashAppType.FlashApp: - { - ((NokiaFlashModel)CurrentModel).DisableRebootTimeOut(); - CurrentInterface = PhoneInterfaces.Lumia_Flash; - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Flash", LogType.FileAndConsole); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - break; - } - case FlashAppType.PhoneInfoApp: - { - CurrentInterface = PhoneInterfaces.Lumia_Bootloader; - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Bootloader (Phone Info)", LogType.FileAndConsole); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - break; - } - } - } - } - else if (e.DevicePath.Contains("DISK&VEN_QUALCOMM&PROD_MMC_STORAGE", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("DISK&VEN_MSFT&PROD_PHONE_MMC_STOR", StringComparison.OrdinalIgnoreCase) || - ((e.DevicePath.Length == @"\\.\E:".Length) && e.DevicePath.StartsWith(@"\\.\") && e.DevicePath.EndsWith(":"))) - { -#if DEBUG - LogFile.Log("Mass storage arrived: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Start new thread for getting metadata.", LogType.FileOnly); -#endif - - // This function is possibly called by an USB notification WndProc. - // It is not possible to invoke COM objects from a WndProc. - // MassStorage uses ManagementObjectSearcher, which is a COM object. - // Therefore we use a new thread. - ThreadPool.QueueUserWorkItem(s => - { - lock (ModelLock) - { - if (!(CurrentModel is MassStorage)) - { - // Wait 1 second to make sure MainOS is loaded - // In case of multiple drive letters being assigned to the phone by the user - // MainOS may take a while to show up and we may accidentally catch up a letter that is - // not for MainOS. - Task.Delay(1000).Wait(); - - MassStorage NewModel = new(e.DevicePath); - - if (NewModel.Drive != null) // When logical drive is already known, we use this model. Or else we wait for the logical drive to arrive. - { - CurrentInterface = PhoneInterfaces.Lumia_MassStorage; - CurrentModel = NewModel; - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Mass storage mode", LogType.FileAndConsole); - if (!string.IsNullOrEmpty(Qcom9006DevicePath)) - { - LogFile.Log("Found 9006 device previously", LogType.FileOnly); - LogFile.Log("Attaching 9006 device", LogType.FileOnly); - NewModel.AttachQualcommSerial(Qcom9006DevicePath); - } - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - } - } - } - }); - } - else if (e.DevicePath.Contains("VID_05C6&", StringComparison.OrdinalIgnoreCase)) // Qualcomm device - { - if (e.DevicePath.Contains("&PID_9008", StringComparison.OrdinalIgnoreCase)) - { - USBDeviceInfo DeviceInfo = Array.Find(USBDevice.GetDevices(((USBNotifier)sender).Guid), (d) => string.Equals(d.DevicePath, e.DevicePath, StringComparison.CurrentCultureIgnoreCase)); - - if ((DeviceInfo.BusName == "QHSUSB_DLOAD") || (DeviceInfo.BusName == "QHSUSB__BULK") || ((DeviceInfo.BusName?.Length == 0) && (LastInterface != PhoneInterfaces.Qualcomm_Download))) // TODO: Separate for Sahara! - { - CurrentInterface = PhoneInterfaces.Qualcomm_Download; - CurrentModel = new QualcommSerial(e.DevicePath); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - if (DeviceInfo.BusName?.Length == 0) - { - LogFile.Log("Driver does not show busname, assume mode: Qualcomm Emergency Download 9008", LogType.FileAndConsole); - } - else - { - LogFile.Log("Mode: Qualcomm Emergency Download 9008", LogType.FileAndConsole); - } - } - else if ((DeviceInfo.BusName == "QHSUSB_ARMPRG") || ((DeviceInfo.BusName?.Length == 0) && (LastInterface == PhoneInterfaces.Qualcomm_Download))) - { - CurrentInterface = PhoneInterfaces.Qualcomm_Flash; - CurrentModel = new QualcommSerial(e.DevicePath); - NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - if (DeviceInfo.BusName?.Length == 0) - { - LogFile.Log("Driver does not show busname, assume mode: Qualcomm Emergency Flash 9008", LogType.FileAndConsole); - } - else - { - LogFile.Log("Mode: Qualcomm Emergency Flash 9008", LogType.FileAndConsole); - } - } - } - else if (e.DevicePath.Contains("&PID_9006", StringComparison.OrdinalIgnoreCase)) - { - // This is part of the Mass Storage inteface. - // It is a slightly different version of the Qualcomm Emergency interface, which is implemented in SBL3. - // One important difference is that the base address for sending a loader is not 0x2A000000, but it is 0x82F00000. - - Qcom9006DevicePath = e.DevicePath; - - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + Qcom9006DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Qualcomm Emergency 9006", LogType.FileAndConsole); - - if (CurrentModel is MassStorage) - { - LogFile.Log("Found Mass Storage device previously", LogType.FileOnly); - LogFile.Log("Attaching 9006 device", LogType.FileOnly); - ((MassStorage)CurrentModel).AttachQualcommSerial(Qcom9006DevicePath); - } - } - else if (e.DevicePath.Contains("&PID_F006", StringComparison.OrdinalIgnoreCase)) - { - // This is part of the charging inteface. - - LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); - LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); - LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); - LogFile.Log("Mode: Qualcomm Emergency Charging F006", LogType.FileAndConsole); - } - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - CurrentModel = null; - CurrentInterface = null; - } - } - - private void InterfaceChanged(PhoneInterfaces NewInterface) - { - CurrentInterface = NewInterface; - } - - private void LumiaNotifier_Removal(object sender, USBEvent e) - { - if (e.DevicePath.Contains("VID_05C6&PID_9006", StringComparison.OrdinalIgnoreCase)) - { - Qcom9006DevicePath = null; - } - - if ( - e.DevicePath.Contains("VID_0421&PID_0660&MI_04", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_0421&PID_0713&MI_04", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_045E&PID_0A01&MI_04", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_0421&PID_0661", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_0421&PID_06FC", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_0421&PID_066E", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_0421&PID_0714", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_0421&PID_05EE", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_045E&PID_0A00", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_045E&PID_0A02", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("VID_05C6&PID_9008", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("DISK&VEN_QUALCOMM&PROD_MMC_STORAGE", StringComparison.OrdinalIgnoreCase) || - e.DevicePath.Contains("DISK&VEN_MSFT&PROD_PHONE_MMC_STOR", StringComparison.OrdinalIgnoreCase) - ) - { - if (CurrentInterface != null) - { - LastInterface = CurrentInterface; - } - - CurrentInterface = null; - if (CurrentModel != null) - { - CurrentModel.Dispose(); - CurrentModel = null; - LogFile.Log("Lumia disconnected", LogType.FileAndConsole); - } - DeviceRemoved(); - } - } - - internal async Task WaitForArrival() - { - IDisposable Result = null; - - if (CurrentInterface == null) - { - LogFile.Log("Waiting for phone to connect...", LogType.FileOnly); - } - - await Task.Run(() => - { - AutoResetEvent e = new(false); - void Arrived(ArrivalEventArgs a) - { - e.Set(); - Result = a.NewModel; - } - NewDeviceArrived += Arrived; - e.WaitOne(); - NewDeviceArrived -= Arrived; - }); - - return Result; - } - - internal async Task WaitForRemoval() - { - LogFile.Log("Waiting for phone to disconnect...", LogType.FileOnly); - - await Task.Run(() => - { - AutoResetEvent e = new(false); - void Removed() => e.Set(); - DeviceRemoved += Removed; - e.WaitOne(); - DeviceRemoved -= Removed; - }); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using MadWizard.WinUSBNet; +using System; +using System.Diagnostics.Eventing.Reader; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace WPinternals +{ + internal delegate void NewDeviceArrivedEvent(ArrivalEventArgs Args); + internal delegate void DeviceRemovedEvent(); + + internal class PhoneNotifierViewModel + { + private USBNotifier LumiaOldCombiNotifier; + private USBNotifier LumiaNewCombiNotifier; + private USBNotifier LumiaNormalNotifier; + private USBNotifier LumiaFlashNotifier; + private USBNotifier StorageNotifier; + private USBNotifier ComPortNotifier; + private USBNotifier LumiaEmergencyNotifier; + private USBNotifier LumiaLabelNotifier; + private USBNotifier HidInterfaceNotifier; + + public PhoneInterfaces? CurrentInterface = null; + private PhoneInterfaces? LastInterface = null; + public IDisposable CurrentModel = null; + + public event NewDeviceArrivedEvent NewDeviceArrived = delegate { }; + public event DeviceRemovedEvent DeviceRemoved = delegate { }; + + private Guid OldCombiInterfaceGuid = new("{0FD3B15C-D457-45d8-A779-C2B2C9F9D0FD}"); + private Guid NewCombiInterfaceGuid = new("{7eaff726-34cc-4204-b09d-f95471b873cf}"); + + private Guid MassStorageInterfaceGuid = new("{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}"); + private Guid ComPortInterfaceGuid = new("{86E0D1E0-8089-11D0-9CE4-08003E301F73}"); + private Guid HidInterfaceGuid = new("{4D1E55B2-F16F-11CF-88CB-001111000030}"); + + private Guid LumiaNormalInterfaceGuid = new("{08324F9C-B621-435C-859B-AE4652481B7C}"); + private Guid LumiaLabelInterfaceGuid = new("{F4FE0C27-7304-4ED7-AAB5-130893B84B6F}"); + private Guid LumiaFlashInterfaceGuid = new("{9e3bd5f7-9690-4fcc-8810-3e2650cd6ecc}"); + private Guid LumiaEmergencyInterfaceGuid = new("{71DE994D-8B7C-43DB-A27E-2AE7CD579A0C}"); + + private readonly object ModelLock = new(); + + private readonly EventWaitHandle NewInterfaceWaitHandle = new(false, EventResetMode.AutoReset); + + private EventLogWatcher LogWatcher; + + private string Qcom9006DevicePath; + + internal void Start() + { + LumiaOldCombiNotifier = new USBNotifier(OldCombiInterfaceGuid); + LumiaOldCombiNotifier.Arrival += LumiaNotifier_Arrival; + LumiaOldCombiNotifier.Removal += LumiaNotifier_Removal; + + LumiaNewCombiNotifier = new USBNotifier(NewCombiInterfaceGuid); + LumiaNewCombiNotifier.Arrival += LumiaNotifier_Arrival; + LumiaNewCombiNotifier.Removal += LumiaNotifier_Removal; + + LumiaNormalNotifier = new USBNotifier(LumiaNormalInterfaceGuid); + LumiaNormalNotifier.Arrival += LumiaNotifier_Arrival; + LumiaNormalNotifier.Removal += LumiaNotifier_Removal; + + LumiaFlashNotifier = new USBNotifier(LumiaFlashInterfaceGuid); + LumiaFlashNotifier.Arrival += LumiaNotifier_Arrival; + LumiaFlashNotifier.Removal += LumiaNotifier_Removal; + + StorageNotifier = new USBNotifier(MassStorageInterfaceGuid); + StorageNotifier.Arrival += LumiaNotifier_Arrival; + StorageNotifier.Removal += LumiaNotifier_Removal; + + ComPortNotifier = new USBNotifier(ComPortInterfaceGuid); + ComPortNotifier.Arrival += LumiaNotifier_Arrival; + ComPortNotifier.Removal += LumiaNotifier_Removal; + + LumiaEmergencyNotifier = new USBNotifier(LumiaEmergencyInterfaceGuid); + LumiaEmergencyNotifier.Arrival += LumiaNotifier_Arrival; + LumiaEmergencyNotifier.Removal += LumiaNotifier_Removal; + + LumiaLabelNotifier = new USBNotifier(LumiaLabelInterfaceGuid); + LumiaLabelNotifier.Arrival += LumiaNotifier_Arrival; + LumiaLabelNotifier.Removal += LumiaNotifier_Removal; + + HidInterfaceNotifier = new USBNotifier(HidInterfaceGuid); + HidInterfaceNotifier.Arrival += LumiaNotifier_Arrival; + HidInterfaceNotifier.Removal += LumiaNotifier_Removal; + + try + { + EventLogQuery LogQuery = new("Microsoft-Windows-Kernel-PnP/Configuration", PathType.LogName, "*[System[(EventID = 411)]]"); + LogWatcher = new EventLogWatcher(LogQuery); + LogWatcher.EventRecordWritten += PnPEventWritten; + LogWatcher.Enabled = true; + App.IsPnPEventLogMissing = false; + } + catch { } + } + + private void PnPEventWritten(Object obj, EventRecordWrittenEventArgs arg) + { + string Description = arg.EventRecord.FormatDescription(); + if (Description.IndexOf("VID_045E&PID_9006", 0, StringComparison.OrdinalIgnoreCase) >= 0) + { + LogFile.Log("Event " + arg.EventRecord.Id.ToString() + ": " + Description, LogType.FileOnly); + LogFile.Log("Phone switched to Mass Storage mode, but the driver on the PC did not start correctly", LogType.FileAndConsole); + CurrentInterface = PhoneInterfaces.Lumia_BadMassStorage; + CurrentModel = null; + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + } + } + + internal void Stop() + { + LumiaOldCombiNotifier.Dispose(); + LumiaNewCombiNotifier.Dispose(); + LumiaNormalNotifier.Dispose(); + LumiaFlashNotifier.Dispose(); + StorageNotifier.Dispose(); + ComPortNotifier.Dispose(); + LumiaEmergencyNotifier.Dispose(); + HidInterfaceNotifier.Dispose(); + LogWatcher.Dispose(); + } + + internal async Task WaitForNextNodeChange() + { + // Node change events are on all USBnotifiers, so we just pick one + await LumiaEmergencyNotifier.WaitForNextNodeChange(); + } + + internal void NotifyArrival() + { + if (CurrentInterface != null) + { + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + } + } + + private void LumiaNotifier_Arrival(object sender, USBEvent e) + { + try + { + if (e.DevicePath.Contains("VID_0421&", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_045E&", StringComparison.OrdinalIgnoreCase)) + { + if (e.DevicePath.Contains("&PID_0660&MI_04", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("&PID_0713&MI_04", StringComparison.OrdinalIgnoreCase) || // for Spec B + e.DevicePath.Contains("&PID_0A01&MI_04", StringComparison.OrdinalIgnoreCase)) // for Spec B (650) + { + CurrentInterface = PhoneInterfaces.Lumia_Label; + CurrentModel = new NokiaPhoneModel(e.DevicePath); + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Label", LogType.FileAndConsole); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + } + else if (e.DevicePath.Contains("&PID_0661", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("&PID_06FC", StringComparison.OrdinalIgnoreCase) || // VID_0421&PID_06FC is for Lumia 930 + e.DevicePath.Contains("&PID_0A00", StringComparison.OrdinalIgnoreCase)) // vid_045e & pid_0a00 & mi_03 = Lumia 950 XL normal mode + { + if (((USBNotifier)sender).Guid == OldCombiInterfaceGuid) + { + NewInterfaceWaitHandle.Reset(); + if (USBDevice.GetDevices(NewCombiInterfaceGuid).Length > 0) + { + return; + } + else + { + // Old combi-interface was detected, but new combi-interface was not detected. + // This could mean 2 things: + // - It is a WP80 phone, which has only this old combi-interface to talk to. + // - It is a WP81 / W10M phone, which has an unresponsive old combi-interface and we need to wait for the new combi-interface to arrive. + // We will wait maximum 1 sec for the new interface. If it doesn't arrive we will start talking on this old interface. + // We will start a new thread, because if this thread is blocked, no new devices will arrive. + string DevicePath = e.DevicePath; + ThreadPool.QueueUserWorkItem(s => + { + if (!NewInterfaceWaitHandle.WaitOne(1000)) + { + // Waithandle not set. + // So new interface did not arrive. + // So we assume we need to talk to this old interface. + + CurrentInterface = PhoneInterfaces.Lumia_Normal; + CurrentModel = new NokiaPhoneModel(DevicePath); + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Normal", LogType.FileAndConsole); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + } + }); + } + } + else + { + NewInterfaceWaitHandle.Set(); + + CurrentInterface = PhoneInterfaces.Lumia_Normal; + CurrentModel = new NokiaPhoneModel(e.DevicePath); + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Normal", LogType.FileAndConsole); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + } + } + else if (e.DevicePath.Contains("&PID_066E", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("&PID_0714", StringComparison.OrdinalIgnoreCase) || // VID_0421&PID_0714 is for Lumia 930 + e.DevicePath.Contains("&PID_0A02", StringComparison.OrdinalIgnoreCase) || // VID_045E&PID_0A02 is for Lumia 950 + e.DevicePath.Contains("&PID_05EE", StringComparison.OrdinalIgnoreCase)) // VID_0421&PID_05EE is for early RX100 + { + CurrentModel = new NokiaFlashModel(e.DevicePath); + ((NokiaFlashModel)CurrentModel).InterfaceChanged += InterfaceChanged; + + FlashAppType type = FlashAppType.FlashApp; + try + { + type = ((NokiaFlashModel)CurrentModel).GetFlashAppType(); + LogFile.Log("Flash App Type: " + type.ToString(), LogType.FileOnly); + } + catch + { + LogFile.Log("Flash App Type could not be determined, assuming " + type.ToString(), LogType.FileOnly); + } + + switch (type) + { + case FlashAppType.BootManager: + { + CurrentInterface = PhoneInterfaces.Lumia_Bootloader; + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Bootloader", LogType.FileAndConsole); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + break; + } + case FlashAppType.FlashApp: + { + ((NokiaFlashModel)CurrentModel).DisableRebootTimeOut(); + CurrentInterface = PhoneInterfaces.Lumia_Flash; + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Flash", LogType.FileAndConsole); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + break; + } + case FlashAppType.PhoneInfoApp: + { + CurrentInterface = PhoneInterfaces.Lumia_Bootloader; + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Bootloader (Phone Info)", LogType.FileAndConsole); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + break; + } + } + } + } + else if (e.DevicePath.Contains("DISK&VEN_QUALCOMM&PROD_MMC_STORAGE", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("DISK&VEN_MSFT&PROD_PHONE_MMC_STOR", StringComparison.OrdinalIgnoreCase) || + ((e.DevicePath.Length == @"\\.\E:".Length) && e.DevicePath.StartsWith(@"\\.\") && e.DevicePath.EndsWith(":"))) + { +#if DEBUG + LogFile.Log("Mass storage arrived: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Start new thread for getting metadata.", LogType.FileOnly); +#endif + + // This function is possibly called by an USB notification WndProc. + // It is not possible to invoke COM objects from a WndProc. + // MassStorage uses ManagementObjectSearcher, which is a COM object. + // Therefore we use a new thread. + ThreadPool.QueueUserWorkItem(s => + { + lock (ModelLock) + { + if (!(CurrentModel is MassStorage)) + { + // Wait 1 second to make sure MainOS is loaded + // In case of multiple drive letters being assigned to the phone by the user + // MainOS may take a while to show up and we may accidentally catch up a letter that is + // not for MainOS. + Task.Delay(1000).Wait(); + + MassStorage NewModel = new(e.DevicePath); + + if (NewModel.Drive != null) // When logical drive is already known, we use this model. Or else we wait for the logical drive to arrive. + { + CurrentInterface = PhoneInterfaces.Lumia_MassStorage; + CurrentModel = NewModel; + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Mass storage mode", LogType.FileAndConsole); + if (!string.IsNullOrEmpty(Qcom9006DevicePath)) + { + LogFile.Log("Found 9006 device previously", LogType.FileOnly); + LogFile.Log("Attaching 9006 device", LogType.FileOnly); + NewModel.AttachQualcommSerial(Qcom9006DevicePath); + } + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + } + } + } + }); + } + else if (e.DevicePath.Contains("VID_05C6&", StringComparison.OrdinalIgnoreCase)) // Qualcomm device + { + if (e.DevicePath.Contains("&PID_9008", StringComparison.OrdinalIgnoreCase)) + { + USBDeviceInfo DeviceInfo = Array.Find(USBDevice.GetDevices(((USBNotifier)sender).Guid), (d) => string.Equals(d.DevicePath, e.DevicePath, StringComparison.CurrentCultureIgnoreCase)); + + if ((DeviceInfo.BusName == "QHSUSB_DLOAD") || (DeviceInfo.BusName == "QHSUSB__BULK") || ((DeviceInfo.BusName?.Length == 0) && (LastInterface != PhoneInterfaces.Qualcomm_Download))) // TODO: Separate for Sahara! + { + CurrentInterface = PhoneInterfaces.Qualcomm_Download; + CurrentModel = new QualcommSerial(e.DevicePath); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + if (DeviceInfo.BusName?.Length == 0) + { + LogFile.Log("Driver does not show busname, assume mode: Qualcomm Emergency Download 9008", LogType.FileAndConsole); + } + else + { + LogFile.Log("Mode: Qualcomm Emergency Download 9008", LogType.FileAndConsole); + } + } + else if ((DeviceInfo.BusName == "QHSUSB_ARMPRG") || ((DeviceInfo.BusName?.Length == 0) && (LastInterface == PhoneInterfaces.Qualcomm_Download))) + { + CurrentInterface = PhoneInterfaces.Qualcomm_Flash; + CurrentModel = new QualcommSerial(e.DevicePath); + NewDeviceArrived(new ArrivalEventArgs((PhoneInterfaces)CurrentInterface, CurrentModel)); + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + if (DeviceInfo.BusName?.Length == 0) + { + LogFile.Log("Driver does not show busname, assume mode: Qualcomm Emergency Flash 9008", LogType.FileAndConsole); + } + else + { + LogFile.Log("Mode: Qualcomm Emergency Flash 9008", LogType.FileAndConsole); + } + } + } + else if (e.DevicePath.Contains("&PID_9006", StringComparison.OrdinalIgnoreCase)) + { + // This is part of the Mass Storage inteface. + // It is a slightly different version of the Qualcomm Emergency interface, which is implemented in SBL3. + // One important difference is that the base address for sending a loader is not 0x2A000000, but it is 0x82F00000. + + Qcom9006DevicePath = e.DevicePath; + + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + Qcom9006DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Qualcomm Emergency 9006", LogType.FileAndConsole); + + if (CurrentModel is MassStorage) + { + LogFile.Log("Found Mass Storage device previously", LogType.FileOnly); + LogFile.Log("Attaching 9006 device", LogType.FileOnly); + ((MassStorage)CurrentModel).AttachQualcommSerial(Qcom9006DevicePath); + } + } + else if (e.DevicePath.Contains("&PID_F006", StringComparison.OrdinalIgnoreCase)) + { + // This is part of the charging inteface. + + LogFile.Log("Found device on interface: " + ((USBNotifier)sender).Guid.ToString(), LogType.FileOnly); + LogFile.Log("Device path: " + e.DevicePath, LogType.FileOnly); + LogFile.Log("Connected device: Lumia", LogType.FileAndConsole); + LogFile.Log("Mode: Qualcomm Emergency Charging F006", LogType.FileAndConsole); + } + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + CurrentModel = null; + CurrentInterface = null; + } + } + + private void InterfaceChanged(PhoneInterfaces NewInterface) + { + CurrentInterface = NewInterface; + } + + private void LumiaNotifier_Removal(object sender, USBEvent e) + { + if (e.DevicePath.Contains("VID_05C6&PID_9006", StringComparison.OrdinalIgnoreCase)) + { + Qcom9006DevicePath = null; + } + + if ( + e.DevicePath.Contains("VID_0421&PID_0660&MI_04", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_0421&PID_0713&MI_04", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_045E&PID_0A01&MI_04", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_0421&PID_0661", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_0421&PID_06FC", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_0421&PID_066E", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_0421&PID_0714", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_0421&PID_05EE", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_045E&PID_0A00", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_045E&PID_0A02", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("VID_05C6&PID_9008", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("DISK&VEN_QUALCOMM&PROD_MMC_STORAGE", StringComparison.OrdinalIgnoreCase) || + e.DevicePath.Contains("DISK&VEN_MSFT&PROD_PHONE_MMC_STOR", StringComparison.OrdinalIgnoreCase) + ) + { + if (CurrentInterface != null) + { + LastInterface = CurrentInterface; + } + + CurrentInterface = null; + if (CurrentModel != null) + { + CurrentModel.Dispose(); + CurrentModel = null; + LogFile.Log("Lumia disconnected", LogType.FileAndConsole); + } + DeviceRemoved(); + } + } + + internal async Task WaitForArrival() + { + IDisposable Result = null; + + if (CurrentInterface == null) + { + LogFile.Log("Waiting for phone to connect...", LogType.FileOnly); + } + + await Task.Run(() => + { + AutoResetEvent e = new(false); + void Arrived(ArrivalEventArgs a) + { + e.Set(); + Result = a.NewModel; + } + NewDeviceArrived += Arrived; + e.WaitOne(); + NewDeviceArrived -= Arrived; + }); + + return Result; + } + + internal async Task WaitForRemoval() + { + LogFile.Log("Waiting for phone to disconnect...", LogType.FileOnly); + + await Task.Run(() => + { + AutoResetEvent e = new(false); + void Removed() => e.Set(); + DeviceRemoved += Removed; + e.WaitOne(); + DeviceRemoved -= Removed; + }); + } + } +} diff --git a/ViewModels/RegistrationViewModel.cs b/WPinternals/ViewModels/RegistrationViewModel.cs similarity index 96% rename from ViewModels/RegistrationViewModel.cs rename to WPinternals/ViewModels/RegistrationViewModel.cs index 1bc6d91..5450fe5 100644 --- a/ViewModels/RegistrationViewModel.cs +++ b/WPinternals/ViewModels/RegistrationViewModel.cs @@ -1,143 +1,143 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Windows; - -namespace WPinternals -{ - internal class RegistrationViewModel : ContextViewModel - { - private readonly Action Completed; - private readonly Action Failed; - - internal RegistrationViewModel(Action Completed, Action Failed) - : base() - { - this.Completed = Completed; - this.Failed = Failed; - } - - private DelegateCommand _ExitCommand = null; - public DelegateCommand ExitCommand - { - get - { - return _ExitCommand ??= new DelegateCommand(() => Application.Current.Shutdown()); - } - } - - private DelegateCommand _ContinueCommand = null; - public DelegateCommand ContinueCommand - { - get - { - return _ContinueCommand ??= new DelegateCommand(() => - { - try - { - App.Config.RegistrationName = _Name; - App.Config.RegistrationEmail = _Email; - App.Config.RegistrationSkypeID = _SkypeID; - App.Config.RegistrationTelegramID = _TelegramID; - App.Config.RegistrationKey = Registration.CalcRegKey(); - - LogFile.BeginAction("Registration"); - LogFile.EndAction("Registration"); - - App.Config.WriteConfig(); - - Completed(); - } - catch - { - Failed(); - } - }); - } - } - - public bool IsRegistrationComplete - { - get - { - return (_Name?.Length >= 2) && (_Email?.Length >= 5); - } - } - - private string _Name = null; - public string Name - { - get - { - return _Name; - } - set - { - _Name = value; - OnPropertyChanged(nameof(Name)); - OnPropertyChanged(nameof(IsRegistrationComplete)); - } - } - - private string _Email = null; - public string Email - { - get - { - return _Email; - } - set - { - _Email = value; - OnPropertyChanged(nameof(Email)); - OnPropertyChanged(nameof(IsRegistrationComplete)); - } - } - - private string _SkypeID = null; - public string SkypeID - { - get - { - return _SkypeID; - } - set - { - _SkypeID = value; - OnPropertyChanged(nameof(SkypeID)); - } - } - - private string _TelegramID = null; - public string TelegramID - { - get - { - return _TelegramID; - } - set - { - _TelegramID = value; - OnPropertyChanged(nameof(TelegramID)); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Windows; + +namespace WPinternals +{ + internal class RegistrationViewModel : ContextViewModel + { + private readonly Action Completed; + private readonly Action Failed; + + internal RegistrationViewModel(Action Completed, Action Failed) + : base() + { + this.Completed = Completed; + this.Failed = Failed; + } + + private DelegateCommand _ExitCommand = null; + public DelegateCommand ExitCommand + { + get + { + return _ExitCommand ??= new DelegateCommand(() => Application.Current.Shutdown()); + } + } + + private DelegateCommand _ContinueCommand = null; + public DelegateCommand ContinueCommand + { + get + { + return _ContinueCommand ??= new DelegateCommand(() => + { + try + { + App.Config.RegistrationName = _Name; + App.Config.RegistrationEmail = _Email; + App.Config.RegistrationSkypeID = _SkypeID; + App.Config.RegistrationTelegramID = _TelegramID; + App.Config.RegistrationKey = Registration.CalcRegKey(); + + LogFile.BeginAction("Registration"); + LogFile.EndAction("Registration"); + + App.Config.WriteConfig(); + + Completed(); + } + catch + { + Failed(); + } + }); + } + } + + public bool IsRegistrationComplete + { + get + { + return (_Name?.Length >= 2) && (_Email?.Length >= 5); + } + } + + private string _Name = null; + public string Name + { + get + { + return _Name; + } + set + { + _Name = value; + OnPropertyChanged(nameof(Name)); + OnPropertyChanged(nameof(IsRegistrationComplete)); + } + } + + private string _Email = null; + public string Email + { + get + { + return _Email; + } + set + { + _Email = value; + OnPropertyChanged(nameof(Email)); + OnPropertyChanged(nameof(IsRegistrationComplete)); + } + } + + private string _SkypeID = null; + public string SkypeID + { + get + { + return _SkypeID; + } + set + { + _SkypeID = value; + OnPropertyChanged(nameof(SkypeID)); + } + } + + private string _TelegramID = null; + public string TelegramID + { + get + { + return _TelegramID; + } + set + { + _TelegramID = value; + OnPropertyChanged(nameof(TelegramID)); + } + } + } +} diff --git a/ViewModels/RestoreSourceSelectionViewModel.cs b/WPinternals/ViewModels/RestoreSourceSelectionViewModel.cs similarity index 97% rename from ViewModels/RestoreSourceSelectionViewModel.cs rename to WPinternals/ViewModels/RestoreSourceSelectionViewModel.cs index 9b06475..59e0c89 100644 --- a/ViewModels/RestoreSourceSelectionViewModel.cs +++ b/WPinternals/ViewModels/RestoreSourceSelectionViewModel.cs @@ -1,204 +1,204 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Threading; - -namespace WPinternals -{ - internal class RestoreSourceSelectionViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - private readonly Action RestoreCallback; - private readonly Action RequestModeSwitch; - internal Action SwitchToUnlockBoot; - internal Action SwitchToFlashRom; - - internal RestoreSourceSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action RequestModeSwitch, Action SwitchToUnlockBoot, Action SwitchToFlashRom, Action RestoreCallback) - : base() - { - this.PhoneNotifier = PhoneNotifier; - this.RestoreCallback = RestoreCallback; - this.RequestModeSwitch = RequestModeSwitch; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.SwitchToFlashRom = SwitchToFlashRom; - - this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - this.PhoneNotifier.DeviceRemoved += DeviceRemoved; - - new Thread(() => EvaluateViewState()).Start(); - } - - private string _EFIESPPath; - public string EFIESPPath - { - get - { - return _EFIESPPath; - } - set - { - if (value != _EFIESPPath) - { - _EFIESPPath = value; - OnPropertyChanged(nameof(EFIESPPath)); - } - } - } - - private string _MainOSPath; - public string MainOSPath - { - get - { - return _MainOSPath; - } - set - { - if (value != _MainOSPath) - { - _MainOSPath = value; - OnPropertyChanged(nameof(MainOSPath)); - } - } - } - - private string _DataPath; - public string DataPath - { - get - { - return _DataPath; - } - set - { - if (value != _DataPath) - { - _DataPath = value; - OnPropertyChanged(nameof(DataPath)); - } - } - } - - private bool _IsPhoneDisconnected; - public bool IsPhoneDisconnected - { - get - { - return _IsPhoneDisconnected; - } - set - { - if (value != _IsPhoneDisconnected) - { - _IsPhoneDisconnected = value; - OnPropertyChanged(nameof(IsPhoneDisconnected)); - } - } - } - - private bool _IsPhoneInFlashMode; - public bool IsPhoneInFlashMode - { - get - { - return _IsPhoneInFlashMode; - } - set - { - if (value != _IsPhoneInFlashMode) - { - _IsPhoneInFlashMode = value; - OnPropertyChanged(nameof(IsPhoneInFlashMode)); - } - } - } - - private bool _IsPhoneInOtherMode; - public bool IsPhoneInOtherMode - { - get - { - return _IsPhoneInOtherMode; - } - set - { - if (value != _IsPhoneInOtherMode) - { - _IsPhoneInOtherMode = value; - OnPropertyChanged(nameof(IsPhoneInOtherMode)); - } - } - } - - private DelegateCommand _RestoreCommand; - public DelegateCommand RestoreCommand - { - get - { - return _RestoreCommand ??= new DelegateCommand(() => RestoreCallback(EFIESPPath, MainOSPath, DataPath), () => ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null)) && (PhoneNotifier.CurrentInterface != null)); - } - } - - ~RestoreSourceSelectionViewModel() - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - new Thread(() => EvaluateViewState()).Start(); - } - - private void DeviceRemoved() - { - new Thread(() => EvaluateViewState()).Start(); - } - - internal override void EvaluateViewState() - { - IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; - IsPhoneInFlashMode = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Flash; - IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInFlashMode; - RestoreCommand.RaiseCanExecuteChanged(); - } - - internal void RebootTo(string Mode) - { - switch (Mode) - { - case "Normal": - RequestModeSwitch(PhoneInterfaces.Lumia_Normal); - break; - case "Flash": - RequestModeSwitch(PhoneInterfaces.Lumia_Flash); - break; - case "Label": - RequestModeSwitch(PhoneInterfaces.Lumia_Label); - break; - case "MassStorage": - RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); - break; - default: - return; - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; + +namespace WPinternals +{ + internal class RestoreSourceSelectionViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + private readonly Action RestoreCallback; + private readonly Action RequestModeSwitch; + internal Action SwitchToUnlockBoot; + internal Action SwitchToFlashRom; + + internal RestoreSourceSelectionViewModel(PhoneNotifierViewModel PhoneNotifier, Action RequestModeSwitch, Action SwitchToUnlockBoot, Action SwitchToFlashRom, Action RestoreCallback) + : base() + { + this.PhoneNotifier = PhoneNotifier; + this.RestoreCallback = RestoreCallback; + this.RequestModeSwitch = RequestModeSwitch; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.SwitchToFlashRom = SwitchToFlashRom; + + this.PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + this.PhoneNotifier.DeviceRemoved += DeviceRemoved; + + new Thread(() => EvaluateViewState()).Start(); + } + + private string _EFIESPPath; + public string EFIESPPath + { + get + { + return _EFIESPPath; + } + set + { + if (value != _EFIESPPath) + { + _EFIESPPath = value; + OnPropertyChanged(nameof(EFIESPPath)); + } + } + } + + private string _MainOSPath; + public string MainOSPath + { + get + { + return _MainOSPath; + } + set + { + if (value != _MainOSPath) + { + _MainOSPath = value; + OnPropertyChanged(nameof(MainOSPath)); + } + } + } + + private string _DataPath; + public string DataPath + { + get + { + return _DataPath; + } + set + { + if (value != _DataPath) + { + _DataPath = value; + OnPropertyChanged(nameof(DataPath)); + } + } + } + + private bool _IsPhoneDisconnected; + public bool IsPhoneDisconnected + { + get + { + return _IsPhoneDisconnected; + } + set + { + if (value != _IsPhoneDisconnected) + { + _IsPhoneDisconnected = value; + OnPropertyChanged(nameof(IsPhoneDisconnected)); + } + } + } + + private bool _IsPhoneInFlashMode; + public bool IsPhoneInFlashMode + { + get + { + return _IsPhoneInFlashMode; + } + set + { + if (value != _IsPhoneInFlashMode) + { + _IsPhoneInFlashMode = value; + OnPropertyChanged(nameof(IsPhoneInFlashMode)); + } + } + } + + private bool _IsPhoneInOtherMode; + public bool IsPhoneInOtherMode + { + get + { + return _IsPhoneInOtherMode; + } + set + { + if (value != _IsPhoneInOtherMode) + { + _IsPhoneInOtherMode = value; + OnPropertyChanged(nameof(IsPhoneInOtherMode)); + } + } + } + + private DelegateCommand _RestoreCommand; + public DelegateCommand RestoreCommand + { + get + { + return _RestoreCommand ??= new DelegateCommand(() => RestoreCallback(EFIESPPath, MainOSPath, DataPath), () => ((EFIESPPath != null) || (MainOSPath != null) || (DataPath != null)) && (PhoneNotifier.CurrentInterface != null)); + } + } + + ~RestoreSourceSelectionViewModel() + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + new Thread(() => EvaluateViewState()).Start(); + } + + private void DeviceRemoved() + { + new Thread(() => EvaluateViewState()).Start(); + } + + internal override void EvaluateViewState() + { + IsPhoneDisconnected = PhoneNotifier.CurrentInterface == null; + IsPhoneInFlashMode = PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Flash; + IsPhoneInOtherMode = !IsPhoneDisconnected && !IsPhoneInFlashMode; + RestoreCommand.RaiseCanExecuteChanged(); + } + + internal void RebootTo(string Mode) + { + switch (Mode) + { + case "Normal": + RequestModeSwitch(PhoneInterfaces.Lumia_Normal); + break; + case "Flash": + RequestModeSwitch(PhoneInterfaces.Lumia_Flash); + break; + case "Label": + RequestModeSwitch(PhoneInterfaces.Lumia_Label); + break; + case "MassStorage": + RequestModeSwitch(PhoneInterfaces.Lumia_MassStorage); + break; + default: + return; + } + } + } +} diff --git a/ViewModels/RestoreViewModel.cs b/WPinternals/ViewModels/RestoreViewModel.cs similarity index 97% rename from ViewModels/RestoreViewModel.cs rename to WPinternals/ViewModels/RestoreViewModel.cs index f44fb62..a5b34a6 100644 --- a/ViewModels/RestoreViewModel.cs +++ b/WPinternals/ViewModels/RestoreViewModel.cs @@ -1,204 +1,204 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Threading; - -namespace WPinternals -{ - internal class RestoreViewModel : ContextViewModel - { - private readonly PhoneNotifierViewModel PhoneNotifier; - private readonly Action Callback; - private readonly Action RequestModeSwitch; - private readonly Action SwitchToUnlockBoot; - private readonly Action SwitchToFlashRom; - - internal RestoreViewModel(PhoneNotifierViewModel PhoneNotifier, Action RequestModeSwitch, Action SwitchToUnlockBoot, Action SwitchToFlashRom, Action Callback) - : base() - { - IsSwitchingInterface = true; - - this.PhoneNotifier = PhoneNotifier; - this.Callback = Callback; - this.RequestModeSwitch = RequestModeSwitch; - this.SwitchToUnlockBoot = SwitchToUnlockBoot; - this.SwitchToFlashRom = SwitchToFlashRom; - } - - internal override void EvaluateViewState() - { - if (!IsActive) - { - return; - } - - if (SubContextViewModel == null) - { - ActivateSubContext(new RestoreSourceSelectionViewModel(PhoneNotifier, RequestModeSwitch, SwitchToUnlockBoot, SwitchToFlashRom, DoRestore)); - } - - if (SubContextViewModel is RestoreSourceSelectionViewModel) - { - ((RestoreSourceSelectionViewModel)SubContextViewModel).EvaluateViewState(); - } - } - - internal async void DoRestore(string EFIESPPath, string MainOSPath, string DataPath) - { - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - RestoreTask(EFIESPPath, MainOSPath, DataPath); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - - internal void RestoreTask(string EFIESPPath, string MainOSPath, string DataPath) - { - new Thread(() => - { - bool Result = true; - - ActivateSubContext(new BusyViewModel("Initializing restore...")); - - ulong TotalSizeSectors = 0; - int PartitionCount = 0; - try - { - if (EFIESPPath != null) - { - TotalSizeSectors += (ulong)new FileInfo(EFIESPPath).Length / 0x200; - PartitionCount++; - } - - if (MainOSPath != null) - { - TotalSizeSectors += (ulong)new FileInfo(MainOSPath).Length / 0x200; - PartitionCount++; - } - - if (DataPath != null) - { - TotalSizeSectors += (ulong)new FileInfo(DataPath).Length / 0x200; - PartitionCount++; - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - - NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; - - BusyViewModel Busy = new("Restoring...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); - ProgressUpdater Updater = Busy.ProgressUpdater; - ActivateSubContext(Busy); - - int i = 0; - if (Result) - { - try - { - if (EFIESPPath != null) - { - i++; - Busy.Message = "Restoring partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.FlashRawPartition(EFIESPPath, "EFIESP", Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (MainOSPath != null) - { - i++; - Busy.Message = "Restoring partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.FlashRawPartition(EFIESPPath, "MainOS", Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (Result) - { - try - { - if (DataPath != null) - { - i++; - Busy.Message = "Restoring partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; - Phone.FlashRawPartition(EFIESPPath, "Data", Updater); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - Result = false; - } - } - - if (!Result) - { - ActivateSubContext(new MessageViewModel("Failed to restore!", Exit)); - return; - } - - ActivateSubContext(new MessageViewModel("Successfully restored!", Exit)); - }).Start(); - } - - internal async void Exit() - { - IsSwitchingInterface = false; - try - { - await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Normal, - (msg, sub) => - ActivateSubContext(new BusyViewModel(msg, sub))); - Callback(); - ActivateSubContext(null); - } - catch (Exception Ex) - { - ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); - } - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Threading; + +namespace WPinternals +{ + internal class RestoreViewModel : ContextViewModel + { + private readonly PhoneNotifierViewModel PhoneNotifier; + private readonly Action Callback; + private readonly Action RequestModeSwitch; + private readonly Action SwitchToUnlockBoot; + private readonly Action SwitchToFlashRom; + + internal RestoreViewModel(PhoneNotifierViewModel PhoneNotifier, Action RequestModeSwitch, Action SwitchToUnlockBoot, Action SwitchToFlashRom, Action Callback) + : base() + { + IsSwitchingInterface = true; + + this.PhoneNotifier = PhoneNotifier; + this.Callback = Callback; + this.RequestModeSwitch = RequestModeSwitch; + this.SwitchToUnlockBoot = SwitchToUnlockBoot; + this.SwitchToFlashRom = SwitchToFlashRom; + } + + internal override void EvaluateViewState() + { + if (!IsActive) + { + return; + } + + if (SubContextViewModel == null) + { + ActivateSubContext(new RestoreSourceSelectionViewModel(PhoneNotifier, RequestModeSwitch, SwitchToUnlockBoot, SwitchToFlashRom, DoRestore)); + } + + if (SubContextViewModel is RestoreSourceSelectionViewModel) + { + ((RestoreSourceSelectionViewModel)SubContextViewModel).EvaluateViewState(); + } + } + + internal async void DoRestore(string EFIESPPath, string MainOSPath, string DataPath) + { + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Flash, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + RestoreTask(EFIESPPath, MainOSPath, DataPath); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + + internal void RestoreTask(string EFIESPPath, string MainOSPath, string DataPath) + { + new Thread(() => + { + bool Result = true; + + ActivateSubContext(new BusyViewModel("Initializing restore...")); + + ulong TotalSizeSectors = 0; + int PartitionCount = 0; + try + { + if (EFIESPPath != null) + { + TotalSizeSectors += (ulong)new FileInfo(EFIESPPath).Length / 0x200; + PartitionCount++; + } + + if (MainOSPath != null) + { + TotalSizeSectors += (ulong)new FileInfo(MainOSPath).Length / 0x200; + PartitionCount++; + } + + if (DataPath != null) + { + TotalSizeSectors += (ulong)new FileInfo(DataPath).Length / 0x200; + PartitionCount++; + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + + NokiaFlashModel Phone = (NokiaFlashModel)PhoneNotifier.CurrentModel; + + BusyViewModel Busy = new("Restoring...", MaxProgressValue: TotalSizeSectors, UIContext: UIContext); + ProgressUpdater Updater = Busy.ProgressUpdater; + ActivateSubContext(Busy); + + int i = 0; + if (Result) + { + try + { + if (EFIESPPath != null) + { + i++; + Busy.Message = "Restoring partition EFIESP (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.FlashRawPartition(EFIESPPath, "EFIESP", Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (MainOSPath != null) + { + i++; + Busy.Message = "Restoring partition MainOS (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.FlashRawPartition(EFIESPPath, "MainOS", Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (Result) + { + try + { + if (DataPath != null) + { + i++; + Busy.Message = "Restoring partition Data (" + i.ToString() + "/" + PartitionCount.ToString() + ")"; + Phone.FlashRawPartition(EFIESPPath, "Data", Updater); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + Result = false; + } + } + + if (!Result) + { + ActivateSubContext(new MessageViewModel("Failed to restore!", Exit)); + return; + } + + ActivateSubContext(new MessageViewModel("Successfully restored!", Exit)); + }).Start(); + } + + internal async void Exit() + { + IsSwitchingInterface = false; + try + { + await SwitchModeViewModel.SwitchToWithProgress(PhoneNotifier, PhoneInterfaces.Lumia_Normal, + (msg, sub) => + ActivateSubContext(new BusyViewModel(msg, sub))); + Callback(); + ActivateSubContext(null); + } + catch (Exception Ex) + { + ActivateSubContext(new MessageViewModel(Ex.Message, Callback)); + } + } + } +} diff --git a/ViewModels/SwitchModeViewModel.cs b/WPinternals/ViewModels/SwitchModeViewModel.cs similarity index 98% rename from ViewModels/SwitchModeViewModel.cs rename to WPinternals/ViewModels/SwitchModeViewModel.cs index 70144f1..871f464 100644 --- a/ViewModels/SwitchModeViewModel.cs +++ b/WPinternals/ViewModels/SwitchModeViewModel.cs @@ -1,918 +1,918 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace WPinternals -{ - internal delegate void ModeSwitchProgressHandler(string Message, string SubMessage); - internal delegate void ModeSwitchErrorHandler(string Message); - internal delegate void ModeSwitchSuccessHandler(IDisposable NewModel, PhoneInterfaces NewInterface); - - internal class SwitchModeViewModel : ContextViewModel - { - protected PhoneNotifierViewModel PhoneNotifier; - protected IDisposable CurrentModel; - protected PhoneInterfaces? CurrentMode; - protected PhoneInterfaces? TargetMode; - protected bool IsSwitching = false; - internal event ModeSwitchProgressHandler ModeSwitchProgress = delegate { }; - internal event ModeSwitchErrorHandler ModeSwitchError = delegate { }; - internal event ModeSwitchSuccessHandler ModeSwitchSuccess = delegate { }; - internal new SetWorkingStatus SetWorkingStatus = (m, s, v, a, st) => { }; - internal new UpdateWorkingStatus UpdateWorkingStatus = (m, s, v, st) => { }; - private string MassStorageWarning = null; - - internal SwitchModeViewModel(PhoneNotifierViewModel PhoneNotifier, PhoneInterfaces? TargetMode) - : base() - { - if ((PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Flash) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Label) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Normal)) - { - throw new ArgumentException(); - } - - this.PhoneNotifier = PhoneNotifier; - this.CurrentModel = (NokiaPhoneModel)PhoneNotifier.CurrentModel; - this.CurrentMode = PhoneNotifier.CurrentInterface; - this.TargetMode = TargetMode; - - if (this.CurrentMode == null) - { - LogFile.Log("Waiting for phone to connect...", LogType.FileAndConsole); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - } - else - { - // Make sure this ViewModel has its View loaded before we continue, - // or else loading of Views can get mixed up. - if (SynchronizationContext.Current == null) - { - StartSwitch(); - } - else - { - SynchronizationContext.Current.Post((s) => ((SwitchModeViewModel)s).StartSwitch(), this); - } - } - } - - internal SwitchModeViewModel(PhoneNotifierViewModel PhoneNotifier, PhoneInterfaces? TargetMode, ModeSwitchProgressHandler ModeSwitchProgress, ModeSwitchErrorHandler ModeSwitchError, ModeSwitchSuccessHandler ModeSwitchSuccess, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null) - : base() - { - if ((PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) && (TargetMode == PhoneInterfaces.Lumia_Flash)) - { - PhoneInfo Info = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(false); - if (Info.BootManagerProtocolVersionMajor >= 2) - { - try - { - // The implementation of SwitchToFlashAppContext() is improved - // SwitchToFlashAppContext() should only be used with BootMgr v2 - // For switching from BootMgr to FlashApp, it will use NOKS - // That will switch to a charging state, whereas a normal context switch will not start charging - // The implementation of NOKS in BootMgr mode has changed in BootMgr v2 - // It does not disconnect / reconnect anymore and the apptype is changed immediately - // NOKS still doesnt return a status - // BootMgr v1 uses normal NOKS and waits for arrival of FlashApp - ((NokiaFlashModel)PhoneNotifier.CurrentModel).SwitchToFlashAppContext(); - - // But this was called as a real switch, so we will raise an arrival event. - PhoneNotifier.CurrentInterface = PhoneInterfaces.Lumia_Flash; - PhoneNotifier.NotifyArrival(); - } - catch { } - } - } - - if (PhoneNotifier.CurrentInterface == TargetMode) - { - ModeSwitchSuccess(PhoneNotifier.CurrentModel, (PhoneInterfaces)PhoneNotifier.CurrentInterface); - } - else - { - this.PhoneNotifier = PhoneNotifier; - this.CurrentModel = (NokiaPhoneModel)PhoneNotifier.CurrentModel; - this.CurrentMode = PhoneNotifier.CurrentInterface; - this.TargetMode = TargetMode; - if (ModeSwitchProgress != null) - { - this.ModeSwitchProgress += ModeSwitchProgress; - } - - if (ModeSwitchError != null) - { - this.ModeSwitchError += ModeSwitchError; - } - - if (ModeSwitchSuccess != null) - { - this.ModeSwitchSuccess += ModeSwitchSuccess; - } - - if (SetWorkingStatus != null) - { - this.SetWorkingStatus = SetWorkingStatus; - } - - if (UpdateWorkingStatus != null) - { - this.UpdateWorkingStatus = UpdateWorkingStatus; - } - - if (this.CurrentMode == null) - { - LogFile.Log("Waiting for phone to connect...", LogType.FileAndConsole); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - } - else - { - // Make sure this ViewModel has its View loaded before we continue, - // or else loading of Views can get mixed up. - if (SynchronizationContext.Current == null) - { - StartSwitch(); - } - else - { - SynchronizationContext.Current.Post((s) => ((SwitchModeViewModel)s).StartSwitch(), this); - } - } - } - } - - private void ModeSwitchProgressWrapper(string Message, string SubMessage) - { - if ((UIContext == null) || (SynchronizationContext.Current == UIContext)) - { - ModeSwitchProgress(Message, SubMessage); - SetWorkingStatus(Message, SubMessage); - } - else - { - UIContext.Post(s => - { - ModeSwitchProgress(Message, SubMessage); - SetWorkingStatus(Message, SubMessage); - }, null); - } - } - - private void ModeSwitchErrorWrapper(string Message) - { - IsSwitching = false; - if ((UIContext == null) || (SynchronizationContext.Current == UIContext)) - { - ModeSwitchError(Message); - } - else - { - UIContext.Post(s => ModeSwitchError(Message), null); - } - } - - private void ModeSwitchSuccessWrapper() - { - IsSwitching = false; - if ((UIContext == null) || (SynchronizationContext.Current == UIContext)) - { - if (PhoneNotifier.CurrentInterface == null) - { - ModeSwitchErrorWrapper("Phone disconnected"); - } - else - { - ModeSwitchSuccess(PhoneNotifier.CurrentModel, (PhoneInterfaces)PhoneNotifier.CurrentInterface); - } - } - else - { - if (PhoneNotifier.CurrentInterface == null) - { - UIContext.Post(s => ModeSwitchErrorWrapper("Phone disconnected"), null); - } - else - { - UIContext.Post(s => ModeSwitchSuccess(PhoneNotifier.CurrentModel, (PhoneInterfaces)PhoneNotifier.CurrentInterface), null); - } - } - } - - ~SwitchModeViewModel() - { - if (PhoneNotifier != null) - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - } - } - - internal void StartSwitch() - { - IsSwitching = true; - - // Make switch and set message or navigate to error - switch (CurrentMode) - { - case PhoneInterfaces.Lumia_Normal: - case PhoneInterfaces.Lumia_Label: - string DeviceMode; - - switch (TargetMode) - { - case PhoneInterfaces.Lumia_Normal: - DeviceMode = "Normal"; - IsSwitchingInterface = true; - ModeSwitchProgressWrapper("Rebooting phone to Normal mode...", null); - LogFile.Log("Rebooting phone to Normal mode", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_Bootloader: - DeviceMode = "Normal"; - IsSwitchingInterface = true; - ModeSwitchProgressWrapper("Rebooting phone to Bootloader mode...", null); - LogFile.Log("Rebooting phone to Bootloader mode", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_Flash: - DeviceMode = "Flash"; - IsSwitchingInterface = true; - ModeSwitchProgressWrapper("Rebooting phone to Flash mode...", null); - LogFile.Log("Rebooting phone to Flash mode", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_Label: - DeviceMode = "Flash"; - IsSwitchingInterface = true; - ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); - LogFile.Log("First rebooting phone to Flash mode (then attempt Label mode)", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_MassStorage: - DeviceMode = "Flash"; - IsSwitchingInterface = true; - ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); - LogFile.Log("First rebooting phone to Flash mode (then attempt Mass Storage mode)", LogType.FileAndConsole); - break; - case PhoneInterfaces.Qualcomm_Download: // Only available in new Lumia's - DeviceMode = "Flash"; - IsSwitchingInterface = true; - ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); - LogFile.Log("First rebooting phone to Flash mode (then attempt Qualcomm Download mode", LogType.FileAndConsole); - break; - default: - return; - } - - Dictionary Params = new(); - Params.Add("DeviceMode", DeviceMode); - Params.Add("ResetMethod", "HwReset"); - try - { - ((NokiaPhoneModel)CurrentModel).ExecuteJsonMethodAsync("SetDeviceMode", Params); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - ModeSwitchErrorWrapper("Failed to switch to Qualcomm Download mode"); - IsSwitchingInterface = false; - } - break; - case PhoneInterfaces.Lumia_Flash: - case PhoneInterfaces.Lumia_Bootloader: - byte[] BootModeFlagCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x46, 0x57, 0x00, 0x55, 0x42, 0x46, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; // NOKFW UBF - byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; // NOKR - byte[] RebootCommandResult; - IsSwitchingInterface = true; - switch (TargetMode) - { - case null: - ((NokiaFlashModel)CurrentModel).Shutdown(); - ModeSwitchProgressWrapper("Please disconnect your device. Waiting...", null); - LogFile.Log("Please disconnect your device. Waiting...", LogType.FileAndConsole); - new Thread(() => - { - PhoneNotifier.WaitForRemoval().Wait(); - ModeSwitchSuccessWrapper(); - }).Start(); - break; - case PhoneInterfaces.Lumia_Normal: - ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper("Rebooting phone to Normal mode...", null); - LogFile.Log("Rebooting phone to Normal mode", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_Bootloader: - ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper("Rebooting phone to Bootloader mode...", null); - LogFile.Log("Rebooting phone to Bootloader mode", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_Label: - SwitchFromFlashToLabelMode(); - break; - case PhoneInterfaces.Lumia_Flash: // attempt to boot from limited flash to full flash - byte[] RebootToFlashCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x53 }; // NOKS - ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootToFlashCommand); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper("Rebooting phone to Flash mode...", null); - LogFile.Log("Rebooting phone to Flash mode", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_MassStorage: - SwitchFromFlashToMassStorageMode(); - break; - case PhoneInterfaces.Qualcomm_Download: - byte[] RebootToQualcommDownloadCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x43, 0x42, 0x45 }; // NOKXCBE - RebootCommandResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(RebootToQualcommDownloadCommand); - if (RebootCommandResult?.Length == 4) // This means fail: NOKU (unknow command) - { - IsSwitchingInterface = false; - ModeSwitchErrorWrapper("Failed to switch to Qualcomm Download mode"); - } - else - { - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper("Rebooting phone to Qualcomm Download mode...", null); - LogFile.Log("Rebooting phone to Qualcomm Download mode", LogType.FileAndConsole); - } - break; - default: - return; - } - break; - case PhoneInterfaces.Lumia_MassStorage: - IsSwitchingInterface = true; - switch (TargetMode) - { - case PhoneInterfaces.Lumia_Normal: - ((MassStorage)CurrentModel).Reboot(); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper("Rebooting phone to Normal mode...", null); - LogFile.Log("Rebooting phone to Normal mode", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_Label: - ((MassStorage)CurrentModel).Reboot(); - PhoneNotifier.NewDeviceArrived += NewDeviceArrivedFromMassStorageMode; - ModeSwitchProgressWrapper("Rebooting phone to Label mode...", null); - LogFile.Log("Rebooting phone to Label mode...", LogType.FileAndConsole); - break; - case PhoneInterfaces.Lumia_Flash: - ((MassStorage)CurrentModel).Reboot(); - PhoneNotifier.NewDeviceArrived += NewDeviceArrivedFromMassStorageMode; - ModeSwitchProgressWrapper("Rebooting phone to Flash mode...", null); - LogFile.Log("Rebooting phone to Flash mode...", LogType.FileAndConsole); - break; - case null: - ((MassStorage)CurrentModel).Reboot(); - PhoneNotifier.NewDeviceArrived += NewDeviceArrivedFromMassStorageMode; - ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); - LogFile.Log("First rebooting phone to Bootloader mode...", LogType.FileAndConsole); - break; - default: - return; - } - break; - case PhoneInterfaces.Qualcomm_Download: - // TODO: don't know how to switch from Qualcomm Download mode to other mode - break; - } - } - - private void NewDeviceArrivedFromMassStorageMode(ArrivalEventArgs Args) - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrivedFromMassStorageMode; - - CurrentModel = Args.NewModel; - CurrentMode = Args.NewInterface; - - // After the mass storage mode reboot command, the phone must be in Bootloader mode. - // If it isn't, something unexpected happened and the phone can't be switched. - // - if (CurrentMode == PhoneInterfaces.Lumia_Bootloader) - { - Task.Run(async () => - { - try - { - await SwitchToWithStatus(PhoneNotifier, TargetMode, SetWorkingStatus, UpdateWorkingStatus); - ModeSwitchSuccessWrapper(); - } - catch - { - switch (TargetMode) - { - case PhoneInterfaces.Lumia_Flash: - ModeSwitchErrorWrapper("Failed to switch to Flash mode"); - break; - case PhoneInterfaces.Lumia_Label: - ModeSwitchErrorWrapper("Failed to switch to Label mode"); - break; - case PhoneInterfaces.Lumia_Normal: - ModeSwitchErrorWrapper("Failed to switch to Normal mode"); - break; - case null: - ModeSwitchSuccessWrapper(); - break; - } - } - }); - } - else - { - switch (TargetMode) - { - case PhoneInterfaces.Lumia_Flash: - ModeSwitchErrorWrapper("Failed to switch to Flash mode"); - break; - case PhoneInterfaces.Lumia_Label: - ModeSwitchErrorWrapper("Failed to switch to Label mode"); - break; - case PhoneInterfaces.Lumia_Normal: - ModeSwitchErrorWrapper("Failed to switch to Normal mode"); - break; - case null: - ModeSwitchErrorWrapper("Failed to shutdown"); - break; - } - } - } - - private void NewDeviceArrived(ArrivalEventArgs Args) - { - PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; - - CurrentModel = Args.NewModel; - CurrentMode = Args.NewInterface; - - if ((CurrentMode == PhoneInterfaces.Lumia_Bootloader) && (TargetMode == PhoneInterfaces.Lumia_Flash)) - { - try - { - // Going from BootMgr to FlashApp - // SwitchToFlashAppContext() will only switch context. Phone will not charge. - // ResetPhoneToFlashMode() reboots to real flash app. Phone will charge. Works when in BootMgrApp, not when already in FlashApp. - - ((NokiaFlashModel)CurrentModel).ResetPhoneToFlashMode(); - CurrentMode = PhoneInterfaces.Lumia_Flash; - PhoneNotifier.NotifyArrival(); - } - catch { } - } - - if (CurrentMode == TargetMode) - { - if (TargetMode == PhoneInterfaces.Lumia_Bootloader) - { - ((NokiaFlashModel)CurrentModel).DisableRebootTimeOut(); - } - - ModeSwitchSuccessWrapper(); - } - else if (!IsSwitching) - { - StartSwitch(); - } - else if ((CurrentMode == PhoneInterfaces.Lumia_Bootloader) && (TargetMode == PhoneInterfaces.Lumia_Normal)) - { - // Do nothing, because booting to Normal shortly goes into Flash mode too. - // Just wait to arrive in Normal mode; - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - } - else if ((CurrentMode == PhoneInterfaces.Lumia_Flash) && (TargetMode == PhoneInterfaces.Lumia_MassStorage)) - { - SwitchFromFlashToMassStorageMode(Continuation: true); - } - else if ((CurrentMode == PhoneInterfaces.Lumia_Flash) && (TargetMode == PhoneInterfaces.Lumia_Label)) - { - SwitchFromFlashToLabelMode(Continuation: true); - } - else if ((CurrentMode == PhoneInterfaces.Lumia_Flash) && (TargetMode == PhoneInterfaces.Qualcomm_Download)) - { - byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; - byte[] RebootToQualcommDownloadCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x43, 0x42, 0x45 }; // NOKXCBE - IsSwitchingInterface = true; - LogFile.Log("Sending command for rebooting to Emergency Download mode"); - byte[] RebootCommandResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(RebootToQualcommDownloadCommand); - if (RebootCommandResult?.Length >= 8) - { - int ResultCode = (RebootCommandResult[6] << 8) + RebootCommandResult[7]; - LogFile.Log("Resultcode: 0x" + ResultCode.ToString("X4")); - } - if (RebootCommandResult?.Length == 4) // This means fail: NOKU (unknow command) - { - ModeSwitchErrorWrapper("Failed to switch to Qualcomm Download mode"); - IsSwitchingInterface = false; - } - else - { - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper("And now rebooting phone to Qualcomm Download mode...", null); - LogFile.Log("Rebooting phone to Qualcomm Download mode"); - } - } - else - { - switch (TargetMode) - { - case PhoneInterfaces.Lumia_Normal: - ModeSwitchErrorWrapper("Failed to switch to Normal mode"); - break; - case PhoneInterfaces.Lumia_Flash: - ModeSwitchErrorWrapper("Failed to switch to Flash mode"); - break; - case PhoneInterfaces.Lumia_Label: - ModeSwitchErrorWrapper("Failed to switch to Label mode"); - break; - case PhoneInterfaces.Lumia_MassStorage: - ModeSwitchErrorWrapper("Failed to switch to Mass Storage mode"); - break; - } - } - } - - private void SwitchFromFlashToLabelMode(bool Continuation = false) - { - string ProgressText = Continuation ? "And now preparing to boot the phone to Label mode..." : "Preparing to boot the phone to Label mode..."; - NokiaFlashModel FlashModel = (NokiaFlashModel)CurrentModel; - if (CurrentMode == PhoneInterfaces.Lumia_Bootloader) - { - try - { - FlashModel.SwitchToFlashAppContext(); - } - catch { } - } - PhoneInfo Info = FlashModel.ReadPhoneInfo(ExtendedInfo: true); - - if (Info.MmosOverUsbSupported) - { - new Thread(() => - { - LogFile.BeginAction("SwitchToLabelMode"); - - try - { - ModeSwitchProgressWrapper(ProgressText, null); - string TempFolder = Environment.GetEnvironmentVariable("TEMP") + @"\WPInternals"; - if (Info.Type == "RM-1152") - { - Info.Type = "RM-1151"; - } - string ENOSWPackage = LumiaDownloadModel.SearchENOSW(Info.Type, Info.Firmware); - SetWorkingStatus("Downloading " + Info.Type + " Test Mode package...", MaxProgressValue: 100); - DownloadEntry downloadEntry = new(ENOSWPackage, TempFolder, null, null, null); - - downloadEntry.PropertyChanged += (object sender, System.ComponentModel.PropertyChangedEventArgs e) => - { - if (e.PropertyName == "Progress") - { - int progress = (sender as DownloadEntry)?.Progress ?? 0; - ulong.TryParse(progress.ToString(), out ulong progressret); - UpdateWorkingStatus(null, CurrentProgressValue: progressret); - - if (progress == 100) - { - ModeSwitchProgressWrapper("Initializing Flash...", null); - - string MMOSPath = TempFolder + "\\" + (sender as DownloadEntry)?.Name; - - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - FileInfo info = new(MMOSPath); - uint length = uint.Parse(info.Length.ToString()); - const int maximumbuffersize = 0x00240000; - uint totalcounts = (uint)Math.Truncate((decimal)length / maximumbuffersize); - - SetWorkingStatus("Flashing Test Mode package...", MaxProgressValue: 100); - - ProgressUpdater progressUpdater = new(totalcounts + 1, (int i, TimeSpan? time) => UpdateWorkingStatus(null, CurrentProgressValue: (ulong)i)); - FlashModel.FlashMMOS(MMOSPath, progressUpdater); - - SetWorkingStatus("And now booting phone to MMOS...", "If the phone stays on the lightning cog screen for a while, you may need to unplug and replug the phone to continue the boot process."); - } - } - }; - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - ModeSwitchErrorWrapper(Ex.Message); - } - - LogFile.EndAction("SwitchToLabelMode"); - }).Start(); - } - else - { - byte[] BootModeFlagCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x46, 0x57, 0x00, 0x55, 0x42, 0x46, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; // NOKFW UBF - byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; // NOKR - - BootModeFlagCommand[0x0F] = 0x59; - ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(BootModeFlagCommand); - ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper("Rebooting phone to Label mode", null); - LogFile.Log("Rebooting phone to Label mode", LogType.FileAndConsole); - } - } - - private void SwitchFromFlashToMassStorageMode(bool Continuation = false) - { - string ProgressText = Continuation ? "And now rebooting phone to Mass Storage mode..." : "Rebooting phone to Mass Storage mode..."; - NokiaFlashModel FlashModel = (NokiaFlashModel)CurrentModel; - if (CurrentMode == PhoneInterfaces.Lumia_Bootloader) - { - try - { - FlashModel.SwitchToFlashAppContext(); - } - catch { } - } - PhoneInfo Info = FlashModel.ReadPhoneInfo(ExtendedInfo: false); - - MassStorageWarning = null; - if (Info.FlashAppProtocolVersionMajor < 2) - { - MassStorageWarning = "Switching to Mass Storage mode should take about 10 seconds. The phone should be unlocked using an Engineering SBL3 to enable Mass Storage mode. When you unlocked the bootloader, but you did not use an Engineering SBL3, an attempt to boot to Mass Storage mode may result in an unresponsive state. Installing drivers for this interface may also cause to hang the PC. So when this switch is taking too long, you should reboot both the PC and the phone. To reboot the phone, you have to perform a soft-reset. Press and hold the volume-down-button and the power-button at the same time for at least 10 seconds. This will trigger a power-cycle and the phone will reboot. Once fully booted, the phone may show strange behavior, like complaining about mail-accounts, showing old text-messages, inability to load https-websites, etc. This is expected behavior, because the time-settings of the phone are incorrect. Just wait a few seconds for the phone to get a data-connection and have the date/time synced. After that the strange behavior will stop automatically and normal operation is resumed."; - } - else - { - MassStorageWarning = "When the screen of the phone is black for a while, it could be that the phone is already in Mass Storage Mode, but there is no drive-letter assigned. To resolve this issue, open Device Manager and manually assign a drive-letter to the MainOS partition of your phone, or open a command-prompt and type: diskpart automount enable."; - if (App.IsPnPEventLogMissing) - { - MassStorageWarning += " It is also possible that the phone is in Mass Storage mode, but the Mass Storage driver on this PC failed. Your PC does not have an eventlog to detect this misbehaviour. But in this case you will see a device with an exclamation mark in Device Manager and then you need to manually reset the phone by pressing and holding the power-button for at least 10 seconds until it vibrates and reboots. After that Windows Phone Internals will revert the changes. After the phone has rebooted to the OS, you can retry to unlock the bootloader."; - } - } - - bool IsOldLumia = Info.FlashAppProtocolVersionMajor < 2; - bool IsNewLumia = Info.FlashAppProtocolVersionMajor >= 2; - bool IsUnlockedNew = false; - if (IsNewLumia) - { - GPT GPT = FlashModel.ReadGPT(); - IsUnlockedNew = (GPT.GetPartition("IS_UNLOCKED") != null) || (GPT.GetPartition("BACKUP_EFIESP") != null) || (GPT.GetPartition("BACKUP_BS_NV") != null); - } - bool IsOriginalEngineeringLumia = !Info.IsBootloaderSecure && !IsUnlockedNew; - - if (IsOldLumia || IsOriginalEngineeringLumia) - { - byte[] BootModeFlagCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x46, 0x57, 0x00, 0x55, 0x42, 0x46, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; // NOKFW UBF - byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; - byte[] RebootToMassStorageCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x4D }; // NOKM - IsSwitchingInterface = true; - byte[] RebootCommandResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(RebootToMassStorageCommand); - if (RebootCommandResult?.Length == 4) // This means fail: NOKU (unknown command) - { - BootModeFlagCommand[0x0F] = 0x4D; - byte[] BootFlagResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(BootModeFlagCommand); - UInt16 ResultCode = BitConverter.ToUInt16(BootFlagResult, 6); - if (ResultCode == 0) - { - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); - ModeSwitchProgressWrapper(ProgressText, MassStorageWarning); - LogFile.Log("Rebooting phone to Mass Storage mode"); - } - else - { - ModeSwitchErrorWrapper("Failed to switch to Mass Storage mode"); - IsSwitchingInterface = false; - } - } - else - { - PhoneNotifier.NewDeviceArrived += NewDeviceArrived; - ModeSwitchProgressWrapper(ProgressText, MassStorageWarning); - LogFile.Log("Rebooting phone to Mass Storage mode"); - } - } - else if (IsUnlockedNew) - { - new Thread(async () => - { - LogFile.BeginAction("SwitchToMassStorageMode"); - - try - { - // Implementation of writing a partition with SecureBoot variable to the phone - ModeSwitchProgressWrapper(ProgressText, MassStorageWarning); - LogFile.Log("Preparing phone for Mass Storage Mode", LogType.FileAndConsole); - var assembly = System.Reflection.Assembly.GetExecutingAssembly(); - - // Magic! - // The SBMSM resource is a compressed version of a raw NV-variable-partition. - // In this partition the SecureBoot variable is disabled and an extra variable is added which triggers Mass Storage Mode on next reboot. - // It overwrites the variable in a different NV-partition than where this variable is stored usually. - // This normally leads to endless-loops when the NV-variables are enumerated. - // But the partition contains an extra hack to break out the endless loops. - using (var stream = assembly.GetManifestResourceStream("WPinternals.SBMSM")) - { - using DecompressedStream dec = new(stream); - using MemoryStream SB = new(); // Must be a seekable stream! - dec.CopyTo(SB); - - // We don't need to check for the BACKUP_BS_NV partition here, - // because the SecureBoot flag is disabled here. - // So either the NV was already backupped or already overwritten. - - GPT GPT = FlashModel.ReadGPT(); - Partition Target = GPT.GetPartition("UEFI_BS_NV"); - - // We've been reading the GPT, so we let the phone reset once more to be sure that memory maps are the same - WPinternalsStatus LastStatus = WPinternalsStatus.Undefined; - List Parts = new(); - FlashPart Part = new(); - Part.StartSector = (uint)Target.FirstSector; - Part.Stream = SB; - Parts.Add(Part); - await LumiaV2UnlockBootViewModel.LumiaV2CustomFlash(PhoneNotifier, null, false, false, Parts, DoResetFirst: true, ClearFlashingStatusAtEnd: false, ShowProgress: false, - SetWorkingStatus: (m, s, v, a, st) => - { - if (SetWorkingStatus != null) - { - if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) - { - SetWorkingStatus(m, s, v, a, st); - } - else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) - { - SetWorkingStatus(ProgressText, MassStorageWarning); - } - - LastStatus = st; - } - }, - UpdateWorkingStatus: (m, s, v, st) => - { - if (UpdateWorkingStatus != null) - { - if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) - { - UpdateWorkingStatus(m, s, v, st); - } - else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) - { - SetWorkingStatus(ProgressText, MassStorageWarning); - } - - LastStatus = st; - } - }); - } - - if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_BadMassStorage) - { - throw new WPinternalsException("Phone is in Mass Storage mode, but the driver on PC failed to start"); - } - - // Wait for bootloader - if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_MassStorage) - { - LogFile.Log("Waiting for Mass Storage Mode (1)...", LogType.FileOnly); - await PhoneNotifier.WaitForArrival(); - } - - if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_BadMassStorage) - { - throw new WPinternalsException("Phone is in Mass Storage mode, but the driver on PC failed to start"); - } - - // Wait for mass storage mode - if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_MassStorage) - { - LogFile.Log("Waiting for Mass Storage Mode (2)...", LogType.FileOnly); - await PhoneNotifier.WaitForArrival(); - } - - if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_BadMassStorage) - { - throw new WPinternalsException("Phone is in Mass Storage mode, but the driver on PC failed to start"); - } - - MassStorage Storage = null; - if (PhoneNotifier.CurrentModel is MassStorage) - { - Storage = (MassStorage)PhoneNotifier.CurrentModel; - } - - if (Storage == null) - { - ModeSwitchErrorWrapper("Failed to switch to Mass Storage Mode"); - } - else - { - ModeSwitchSuccessWrapper(); - } - } - catch (Exception Ex) - { - LogFile.LogException(Ex); - ModeSwitchErrorWrapper(Ex.Message); - } - - LogFile.EndAction("SwitchToMassStorageMode"); - }).Start(); - } - else - { - ModeSwitchErrorWrapper("Bootloader was not unlocked. First unlock bootloader before you try to switch to Mass Storage Mode."); - } - } - - internal async static Task SwitchTo(PhoneNotifierViewModel Notifier, PhoneInterfaces? TargetMode, string RequestedVolumeForMassStorage = "MainOS") - { - return await SwitchToWithProgress(Notifier, TargetMode, null, RequestedVolumeForMassStorage); - } - - internal async static Task SwitchToWithProgress(PhoneNotifierViewModel Notifier, PhoneInterfaces? TargetMode, ModeSwitchProgressHandler ModeSwitchProgress, string RequestedVolumeForMassStorage = "MainOS") - { - if (Notifier.CurrentInterface == TargetMode) - { - return Notifier.CurrentModel; - } - - IDisposable Result = null; - string LocalErrorMessage = null; - - AsyncAutoResetEvent Event = new(false); - - SwitchModeViewModel Switch = new( - Notifier, - TargetMode, - ModeSwitchProgress, - (string ErrorMessage) => - { - LocalErrorMessage = ErrorMessage; - Event.Set(); - }, - (IDisposable NewModel, PhoneInterfaces NewInterface) => - { - Result = NewModel; - Event.Set(); - } - ); - - await Event.WaitAsync(Timeout.InfiniteTimeSpan); - - if (LocalErrorMessage != null) - { - throw new WPinternalsException(LocalErrorMessage); - } - - return Result; - } - - internal async static Task SwitchToWithStatus(PhoneNotifierViewModel Notifier, PhoneInterfaces? TargetMode, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, string RequestedVolumeForMassStorage = "MainOS") - { - if (Notifier.CurrentInterface == TargetMode) - { - return Notifier.CurrentModel; - } - - IDisposable Result = null; - string LocalErrorMessage = null; - - AsyncAutoResetEvent Event = new(false); - - SwitchModeViewModel Switch = new( - Notifier, - TargetMode, - null, - (string ErrorMessage) => - { - LocalErrorMessage = ErrorMessage; - Event.Set(); - }, - (IDisposable NewModel, PhoneInterfaces NewInterface) => - { - Result = NewModel; - Event.Set(); - }, SetWorkingStatus, UpdateWorkingStatus - ); - - await Event.WaitAsync(Timeout.InfiniteTimeSpan); - - if (LocalErrorMessage != null) - { - throw new WPinternalsException(LocalErrorMessage); - } - - return Result; - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace WPinternals +{ + internal delegate void ModeSwitchProgressHandler(string Message, string SubMessage); + internal delegate void ModeSwitchErrorHandler(string Message); + internal delegate void ModeSwitchSuccessHandler(IDisposable NewModel, PhoneInterfaces NewInterface); + + internal class SwitchModeViewModel : ContextViewModel + { + protected PhoneNotifierViewModel PhoneNotifier; + protected IDisposable CurrentModel; + protected PhoneInterfaces? CurrentMode; + protected PhoneInterfaces? TargetMode; + protected bool IsSwitching = false; + internal event ModeSwitchProgressHandler ModeSwitchProgress = delegate { }; + internal event ModeSwitchErrorHandler ModeSwitchError = delegate { }; + internal event ModeSwitchSuccessHandler ModeSwitchSuccess = delegate { }; + internal new SetWorkingStatus SetWorkingStatus = (m, s, v, a, st) => { }; + internal new UpdateWorkingStatus UpdateWorkingStatus = (m, s, v, st) => { }; + private string MassStorageWarning = null; + + internal SwitchModeViewModel(PhoneNotifierViewModel PhoneNotifier, PhoneInterfaces? TargetMode) + : base() + { + if ((PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Flash) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Bootloader) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Label) && (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_Normal)) + { + throw new ArgumentException(); + } + + this.PhoneNotifier = PhoneNotifier; + this.CurrentModel = (NokiaPhoneModel)PhoneNotifier.CurrentModel; + this.CurrentMode = PhoneNotifier.CurrentInterface; + this.TargetMode = TargetMode; + + if (this.CurrentMode == null) + { + LogFile.Log("Waiting for phone to connect...", LogType.FileAndConsole); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + } + else + { + // Make sure this ViewModel has its View loaded before we continue, + // or else loading of Views can get mixed up. + if (SynchronizationContext.Current == null) + { + StartSwitch(); + } + else + { + SynchronizationContext.Current.Post((s) => ((SwitchModeViewModel)s).StartSwitch(), this); + } + } + } + + internal SwitchModeViewModel(PhoneNotifierViewModel PhoneNotifier, PhoneInterfaces? TargetMode, ModeSwitchProgressHandler ModeSwitchProgress, ModeSwitchErrorHandler ModeSwitchError, ModeSwitchSuccessHandler ModeSwitchSuccess, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null) + : base() + { + if ((PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_Bootloader) && (TargetMode == PhoneInterfaces.Lumia_Flash)) + { + PhoneInfo Info = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(false); + if (Info.BootManagerProtocolVersionMajor >= 2) + { + try + { + // The implementation of SwitchToFlashAppContext() is improved + // SwitchToFlashAppContext() should only be used with BootMgr v2 + // For switching from BootMgr to FlashApp, it will use NOKS + // That will switch to a charging state, whereas a normal context switch will not start charging + // The implementation of NOKS in BootMgr mode has changed in BootMgr v2 + // It does not disconnect / reconnect anymore and the apptype is changed immediately + // NOKS still doesnt return a status + // BootMgr v1 uses normal NOKS and waits for arrival of FlashApp + ((NokiaFlashModel)PhoneNotifier.CurrentModel).SwitchToFlashAppContext(); + + // But this was called as a real switch, so we will raise an arrival event. + PhoneNotifier.CurrentInterface = PhoneInterfaces.Lumia_Flash; + PhoneNotifier.NotifyArrival(); + } + catch { } + } + } + + if (PhoneNotifier.CurrentInterface == TargetMode) + { + ModeSwitchSuccess(PhoneNotifier.CurrentModel, (PhoneInterfaces)PhoneNotifier.CurrentInterface); + } + else + { + this.PhoneNotifier = PhoneNotifier; + this.CurrentModel = (NokiaPhoneModel)PhoneNotifier.CurrentModel; + this.CurrentMode = PhoneNotifier.CurrentInterface; + this.TargetMode = TargetMode; + if (ModeSwitchProgress != null) + { + this.ModeSwitchProgress += ModeSwitchProgress; + } + + if (ModeSwitchError != null) + { + this.ModeSwitchError += ModeSwitchError; + } + + if (ModeSwitchSuccess != null) + { + this.ModeSwitchSuccess += ModeSwitchSuccess; + } + + if (SetWorkingStatus != null) + { + this.SetWorkingStatus = SetWorkingStatus; + } + + if (UpdateWorkingStatus != null) + { + this.UpdateWorkingStatus = UpdateWorkingStatus; + } + + if (this.CurrentMode == null) + { + LogFile.Log("Waiting for phone to connect...", LogType.FileAndConsole); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + } + else + { + // Make sure this ViewModel has its View loaded before we continue, + // or else loading of Views can get mixed up. + if (SynchronizationContext.Current == null) + { + StartSwitch(); + } + else + { + SynchronizationContext.Current.Post((s) => ((SwitchModeViewModel)s).StartSwitch(), this); + } + } + } + } + + private void ModeSwitchProgressWrapper(string Message, string SubMessage) + { + if ((UIContext == null) || (SynchronizationContext.Current == UIContext)) + { + ModeSwitchProgress(Message, SubMessage); + SetWorkingStatus(Message, SubMessage); + } + else + { + UIContext.Post(s => + { + ModeSwitchProgress(Message, SubMessage); + SetWorkingStatus(Message, SubMessage); + }, null); + } + } + + private void ModeSwitchErrorWrapper(string Message) + { + IsSwitching = false; + if ((UIContext == null) || (SynchronizationContext.Current == UIContext)) + { + ModeSwitchError(Message); + } + else + { + UIContext.Post(s => ModeSwitchError(Message), null); + } + } + + private void ModeSwitchSuccessWrapper() + { + IsSwitching = false; + if ((UIContext == null) || (SynchronizationContext.Current == UIContext)) + { + if (PhoneNotifier.CurrentInterface == null) + { + ModeSwitchErrorWrapper("Phone disconnected"); + } + else + { + ModeSwitchSuccess(PhoneNotifier.CurrentModel, (PhoneInterfaces)PhoneNotifier.CurrentInterface); + } + } + else + { + if (PhoneNotifier.CurrentInterface == null) + { + UIContext.Post(s => ModeSwitchErrorWrapper("Phone disconnected"), null); + } + else + { + UIContext.Post(s => ModeSwitchSuccess(PhoneNotifier.CurrentModel, (PhoneInterfaces)PhoneNotifier.CurrentInterface), null); + } + } + } + + ~SwitchModeViewModel() + { + if (PhoneNotifier != null) + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + } + } + + internal void StartSwitch() + { + IsSwitching = true; + + // Make switch and set message or navigate to error + switch (CurrentMode) + { + case PhoneInterfaces.Lumia_Normal: + case PhoneInterfaces.Lumia_Label: + string DeviceMode; + + switch (TargetMode) + { + case PhoneInterfaces.Lumia_Normal: + DeviceMode = "Normal"; + IsSwitchingInterface = true; + ModeSwitchProgressWrapper("Rebooting phone to Normal mode...", null); + LogFile.Log("Rebooting phone to Normal mode", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_Bootloader: + DeviceMode = "Normal"; + IsSwitchingInterface = true; + ModeSwitchProgressWrapper("Rebooting phone to Bootloader mode...", null); + LogFile.Log("Rebooting phone to Bootloader mode", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_Flash: + DeviceMode = "Flash"; + IsSwitchingInterface = true; + ModeSwitchProgressWrapper("Rebooting phone to Flash mode...", null); + LogFile.Log("Rebooting phone to Flash mode", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_Label: + DeviceMode = "Flash"; + IsSwitchingInterface = true; + ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); + LogFile.Log("First rebooting phone to Flash mode (then attempt Label mode)", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_MassStorage: + DeviceMode = "Flash"; + IsSwitchingInterface = true; + ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); + LogFile.Log("First rebooting phone to Flash mode (then attempt Mass Storage mode)", LogType.FileAndConsole); + break; + case PhoneInterfaces.Qualcomm_Download: // Only available in new Lumia's + DeviceMode = "Flash"; + IsSwitchingInterface = true; + ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); + LogFile.Log("First rebooting phone to Flash mode (then attempt Qualcomm Download mode", LogType.FileAndConsole); + break; + default: + return; + } + + Dictionary Params = new(); + Params.Add("DeviceMode", DeviceMode); + Params.Add("ResetMethod", "HwReset"); + try + { + ((NokiaPhoneModel)CurrentModel).ExecuteJsonMethodAsync("SetDeviceMode", Params); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + ModeSwitchErrorWrapper("Failed to switch to Qualcomm Download mode"); + IsSwitchingInterface = false; + } + break; + case PhoneInterfaces.Lumia_Flash: + case PhoneInterfaces.Lumia_Bootloader: + byte[] BootModeFlagCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x46, 0x57, 0x00, 0x55, 0x42, 0x46, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; // NOKFW UBF + byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; // NOKR + byte[] RebootCommandResult; + IsSwitchingInterface = true; + switch (TargetMode) + { + case null: + ((NokiaFlashModel)CurrentModel).Shutdown(); + ModeSwitchProgressWrapper("Please disconnect your device. Waiting...", null); + LogFile.Log("Please disconnect your device. Waiting...", LogType.FileAndConsole); + new Thread(() => + { + PhoneNotifier.WaitForRemoval().Wait(); + ModeSwitchSuccessWrapper(); + }).Start(); + break; + case PhoneInterfaces.Lumia_Normal: + ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper("Rebooting phone to Normal mode...", null); + LogFile.Log("Rebooting phone to Normal mode", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_Bootloader: + ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper("Rebooting phone to Bootloader mode...", null); + LogFile.Log("Rebooting phone to Bootloader mode", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_Label: + SwitchFromFlashToLabelMode(); + break; + case PhoneInterfaces.Lumia_Flash: // attempt to boot from limited flash to full flash + byte[] RebootToFlashCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x53 }; // NOKS + ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootToFlashCommand); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper("Rebooting phone to Flash mode...", null); + LogFile.Log("Rebooting phone to Flash mode", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_MassStorage: + SwitchFromFlashToMassStorageMode(); + break; + case PhoneInterfaces.Qualcomm_Download: + byte[] RebootToQualcommDownloadCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x43, 0x42, 0x45 }; // NOKXCBE + RebootCommandResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(RebootToQualcommDownloadCommand); + if (RebootCommandResult?.Length == 4) // This means fail: NOKU (unknow command) + { + IsSwitchingInterface = false; + ModeSwitchErrorWrapper("Failed to switch to Qualcomm Download mode"); + } + else + { + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper("Rebooting phone to Qualcomm Download mode...", null); + LogFile.Log("Rebooting phone to Qualcomm Download mode", LogType.FileAndConsole); + } + break; + default: + return; + } + break; + case PhoneInterfaces.Lumia_MassStorage: + IsSwitchingInterface = true; + switch (TargetMode) + { + case PhoneInterfaces.Lumia_Normal: + ((MassStorage)CurrentModel).Reboot(); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper("Rebooting phone to Normal mode...", null); + LogFile.Log("Rebooting phone to Normal mode", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_Label: + ((MassStorage)CurrentModel).Reboot(); + PhoneNotifier.NewDeviceArrived += NewDeviceArrivedFromMassStorageMode; + ModeSwitchProgressWrapper("Rebooting phone to Label mode...", null); + LogFile.Log("Rebooting phone to Label mode...", LogType.FileAndConsole); + break; + case PhoneInterfaces.Lumia_Flash: + ((MassStorage)CurrentModel).Reboot(); + PhoneNotifier.NewDeviceArrived += NewDeviceArrivedFromMassStorageMode; + ModeSwitchProgressWrapper("Rebooting phone to Flash mode...", null); + LogFile.Log("Rebooting phone to Flash mode...", LogType.FileAndConsole); + break; + case null: + ((MassStorage)CurrentModel).Reboot(); + PhoneNotifier.NewDeviceArrived += NewDeviceArrivedFromMassStorageMode; + ModeSwitchProgressWrapper("First rebooting phone to Flash mode...", null); + LogFile.Log("First rebooting phone to Bootloader mode...", LogType.FileAndConsole); + break; + default: + return; + } + break; + case PhoneInterfaces.Qualcomm_Download: + // TODO: don't know how to switch from Qualcomm Download mode to other mode + break; + } + } + + private void NewDeviceArrivedFromMassStorageMode(ArrivalEventArgs Args) + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrivedFromMassStorageMode; + + CurrentModel = Args.NewModel; + CurrentMode = Args.NewInterface; + + // After the mass storage mode reboot command, the phone must be in Bootloader mode. + // If it isn't, something unexpected happened and the phone can't be switched. + // + if (CurrentMode == PhoneInterfaces.Lumia_Bootloader) + { + Task.Run(async () => + { + try + { + await SwitchToWithStatus(PhoneNotifier, TargetMode, SetWorkingStatus, UpdateWorkingStatus); + ModeSwitchSuccessWrapper(); + } + catch + { + switch (TargetMode) + { + case PhoneInterfaces.Lumia_Flash: + ModeSwitchErrorWrapper("Failed to switch to Flash mode"); + break; + case PhoneInterfaces.Lumia_Label: + ModeSwitchErrorWrapper("Failed to switch to Label mode"); + break; + case PhoneInterfaces.Lumia_Normal: + ModeSwitchErrorWrapper("Failed to switch to Normal mode"); + break; + case null: + ModeSwitchSuccessWrapper(); + break; + } + } + }); + } + else + { + switch (TargetMode) + { + case PhoneInterfaces.Lumia_Flash: + ModeSwitchErrorWrapper("Failed to switch to Flash mode"); + break; + case PhoneInterfaces.Lumia_Label: + ModeSwitchErrorWrapper("Failed to switch to Label mode"); + break; + case PhoneInterfaces.Lumia_Normal: + ModeSwitchErrorWrapper("Failed to switch to Normal mode"); + break; + case null: + ModeSwitchErrorWrapper("Failed to shutdown"); + break; + } + } + } + + private void NewDeviceArrived(ArrivalEventArgs Args) + { + PhoneNotifier.NewDeviceArrived -= NewDeviceArrived; + + CurrentModel = Args.NewModel; + CurrentMode = Args.NewInterface; + + if ((CurrentMode == PhoneInterfaces.Lumia_Bootloader) && (TargetMode == PhoneInterfaces.Lumia_Flash)) + { + try + { + // Going from BootMgr to FlashApp + // SwitchToFlashAppContext() will only switch context. Phone will not charge. + // ResetPhoneToFlashMode() reboots to real flash app. Phone will charge. Works when in BootMgrApp, not when already in FlashApp. + + ((NokiaFlashModel)CurrentModel).ResetPhoneToFlashMode(); + CurrentMode = PhoneInterfaces.Lumia_Flash; + PhoneNotifier.NotifyArrival(); + } + catch { } + } + + if (CurrentMode == TargetMode) + { + if (TargetMode == PhoneInterfaces.Lumia_Bootloader) + { + ((NokiaFlashModel)CurrentModel).DisableRebootTimeOut(); + } + + ModeSwitchSuccessWrapper(); + } + else if (!IsSwitching) + { + StartSwitch(); + } + else if ((CurrentMode == PhoneInterfaces.Lumia_Bootloader) && (TargetMode == PhoneInterfaces.Lumia_Normal)) + { + // Do nothing, because booting to Normal shortly goes into Flash mode too. + // Just wait to arrive in Normal mode; + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + } + else if ((CurrentMode == PhoneInterfaces.Lumia_Flash) && (TargetMode == PhoneInterfaces.Lumia_MassStorage)) + { + SwitchFromFlashToMassStorageMode(Continuation: true); + } + else if ((CurrentMode == PhoneInterfaces.Lumia_Flash) && (TargetMode == PhoneInterfaces.Lumia_Label)) + { + SwitchFromFlashToLabelMode(Continuation: true); + } + else if ((CurrentMode == PhoneInterfaces.Lumia_Flash) && (TargetMode == PhoneInterfaces.Qualcomm_Download)) + { + byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; + byte[] RebootToQualcommDownloadCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x43, 0x42, 0x45 }; // NOKXCBE + IsSwitchingInterface = true; + LogFile.Log("Sending command for rebooting to Emergency Download mode"); + byte[] RebootCommandResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(RebootToQualcommDownloadCommand); + if (RebootCommandResult?.Length >= 8) + { + int ResultCode = (RebootCommandResult[6] << 8) + RebootCommandResult[7]; + LogFile.Log("Resultcode: 0x" + ResultCode.ToString("X4")); + } + if (RebootCommandResult?.Length == 4) // This means fail: NOKU (unknow command) + { + ModeSwitchErrorWrapper("Failed to switch to Qualcomm Download mode"); + IsSwitchingInterface = false; + } + else + { + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper("And now rebooting phone to Qualcomm Download mode...", null); + LogFile.Log("Rebooting phone to Qualcomm Download mode"); + } + } + else + { + switch (TargetMode) + { + case PhoneInterfaces.Lumia_Normal: + ModeSwitchErrorWrapper("Failed to switch to Normal mode"); + break; + case PhoneInterfaces.Lumia_Flash: + ModeSwitchErrorWrapper("Failed to switch to Flash mode"); + break; + case PhoneInterfaces.Lumia_Label: + ModeSwitchErrorWrapper("Failed to switch to Label mode"); + break; + case PhoneInterfaces.Lumia_MassStorage: + ModeSwitchErrorWrapper("Failed to switch to Mass Storage mode"); + break; + } + } + } + + private void SwitchFromFlashToLabelMode(bool Continuation = false) + { + string ProgressText = Continuation ? "And now preparing to boot the phone to Label mode..." : "Preparing to boot the phone to Label mode..."; + NokiaFlashModel FlashModel = (NokiaFlashModel)CurrentModel; + if (CurrentMode == PhoneInterfaces.Lumia_Bootloader) + { + try + { + FlashModel.SwitchToFlashAppContext(); + } + catch { } + } + PhoneInfo Info = FlashModel.ReadPhoneInfo(ExtendedInfo: true); + + if (Info.MmosOverUsbSupported) + { + new Thread(() => + { + LogFile.BeginAction("SwitchToLabelMode"); + + try + { + ModeSwitchProgressWrapper(ProgressText, null); + string TempFolder = Environment.GetEnvironmentVariable("TEMP") + @"\WPInternals"; + if (Info.Type == "RM-1152") + { + Info.Type = "RM-1151"; + } + string ENOSWPackage = LumiaDownloadModel.SearchENOSW(Info.Type, Info.Firmware); + SetWorkingStatus("Downloading " + Info.Type + " Test Mode package...", MaxProgressValue: 100); + DownloadEntry downloadEntry = new(ENOSWPackage, TempFolder, null, null, null); + + downloadEntry.PropertyChanged += (object sender, System.ComponentModel.PropertyChangedEventArgs e) => + { + if (e.PropertyName == "Progress") + { + int progress = (sender as DownloadEntry)?.Progress ?? 0; + ulong.TryParse(progress.ToString(), out ulong progressret); + UpdateWorkingStatus(null, CurrentProgressValue: progressret); + + if (progress == 100) + { + ModeSwitchProgressWrapper("Initializing Flash...", null); + + string MMOSPath = TempFolder + "\\" + (sender as DownloadEntry)?.Name; + + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + FileInfo info = new(MMOSPath); + uint length = uint.Parse(info.Length.ToString()); + const int maximumbuffersize = 0x00240000; + uint totalcounts = (uint)Math.Truncate((decimal)length / maximumbuffersize); + + SetWorkingStatus("Flashing Test Mode package...", MaxProgressValue: 100); + + ProgressUpdater progressUpdater = new(totalcounts + 1, (int i, TimeSpan? time) => UpdateWorkingStatus(null, CurrentProgressValue: (ulong)i)); + FlashModel.FlashMMOS(MMOSPath, progressUpdater); + + SetWorkingStatus("And now booting phone to MMOS...", "If the phone stays on the lightning cog screen for a while, you may need to unplug and replug the phone to continue the boot process."); + } + } + }; + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + ModeSwitchErrorWrapper(Ex.Message); + } + + LogFile.EndAction("SwitchToLabelMode"); + }).Start(); + } + else + { + byte[] BootModeFlagCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x46, 0x57, 0x00, 0x55, 0x42, 0x46, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; // NOKFW UBF + byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; // NOKR + + BootModeFlagCommand[0x0F] = 0x59; + ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(BootModeFlagCommand); + ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper("Rebooting phone to Label mode", null); + LogFile.Log("Rebooting phone to Label mode", LogType.FileAndConsole); + } + } + + private void SwitchFromFlashToMassStorageMode(bool Continuation = false) + { + string ProgressText = Continuation ? "And now rebooting phone to Mass Storage mode..." : "Rebooting phone to Mass Storage mode..."; + NokiaFlashModel FlashModel = (NokiaFlashModel)CurrentModel; + if (CurrentMode == PhoneInterfaces.Lumia_Bootloader) + { + try + { + FlashModel.SwitchToFlashAppContext(); + } + catch { } + } + PhoneInfo Info = FlashModel.ReadPhoneInfo(ExtendedInfo: false); + + MassStorageWarning = null; + if (Info.FlashAppProtocolVersionMajor < 2) + { + MassStorageWarning = "Switching to Mass Storage mode should take about 10 seconds. The phone should be unlocked using an Engineering SBL3 to enable Mass Storage mode. When you unlocked the bootloader, but you did not use an Engineering SBL3, an attempt to boot to Mass Storage mode may result in an unresponsive state. Installing drivers for this interface may also cause to hang the PC. So when this switch is taking too long, you should reboot both the PC and the phone. To reboot the phone, you have to perform a soft-reset. Press and hold the volume-down-button and the power-button at the same time for at least 10 seconds. This will trigger a power-cycle and the phone will reboot. Once fully booted, the phone may show strange behavior, like complaining about mail-accounts, showing old text-messages, inability to load https-websites, etc. This is expected behavior, because the time-settings of the phone are incorrect. Just wait a few seconds for the phone to get a data-connection and have the date/time synced. After that the strange behavior will stop automatically and normal operation is resumed."; + } + else + { + MassStorageWarning = "When the screen of the phone is black for a while, it could be that the phone is already in Mass Storage Mode, but there is no drive-letter assigned. To resolve this issue, open Device Manager and manually assign a drive-letter to the MainOS partition of your phone, or open a command-prompt and type: diskpart automount enable."; + if (App.IsPnPEventLogMissing) + { + MassStorageWarning += " It is also possible that the phone is in Mass Storage mode, but the Mass Storage driver on this PC failed. Your PC does not have an eventlog to detect this misbehaviour. But in this case you will see a device with an exclamation mark in Device Manager and then you need to manually reset the phone by pressing and holding the power-button for at least 10 seconds until it vibrates and reboots. After that Windows Phone Internals will revert the changes. After the phone has rebooted to the OS, you can retry to unlock the bootloader."; + } + } + + bool IsOldLumia = Info.FlashAppProtocolVersionMajor < 2; + bool IsNewLumia = Info.FlashAppProtocolVersionMajor >= 2; + bool IsUnlockedNew = false; + if (IsNewLumia) + { + GPT GPT = FlashModel.ReadGPT(); + IsUnlockedNew = (GPT.GetPartition("IS_UNLOCKED") != null) || (GPT.GetPartition("BACKUP_EFIESP") != null) || (GPT.GetPartition("BACKUP_BS_NV") != null); + } + bool IsOriginalEngineeringLumia = !Info.IsBootloaderSecure && !IsUnlockedNew; + + if (IsOldLumia || IsOriginalEngineeringLumia) + { + byte[] BootModeFlagCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x58, 0x46, 0x57, 0x00, 0x55, 0x42, 0x46, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; // NOKFW UBF + byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; + byte[] RebootToMassStorageCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x4D }; // NOKM + IsSwitchingInterface = true; + byte[] RebootCommandResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(RebootToMassStorageCommand); + if (RebootCommandResult?.Length == 4) // This means fail: NOKU (unknown command) + { + BootModeFlagCommand[0x0F] = 0x4D; + byte[] BootFlagResult = ((NokiaPhoneModel)CurrentModel).ExecuteRawMethod(BootModeFlagCommand); + UInt16 ResultCode = BitConverter.ToUInt16(BootFlagResult, 6); + if (ResultCode == 0) + { + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ((NokiaPhoneModel)CurrentModel).ExecuteRawVoidMethod(RebootCommand); + ModeSwitchProgressWrapper(ProgressText, MassStorageWarning); + LogFile.Log("Rebooting phone to Mass Storage mode"); + } + else + { + ModeSwitchErrorWrapper("Failed to switch to Mass Storage mode"); + IsSwitchingInterface = false; + } + } + else + { + PhoneNotifier.NewDeviceArrived += NewDeviceArrived; + ModeSwitchProgressWrapper(ProgressText, MassStorageWarning); + LogFile.Log("Rebooting phone to Mass Storage mode"); + } + } + else if (IsUnlockedNew) + { + new Thread(async () => + { + LogFile.BeginAction("SwitchToMassStorageMode"); + + try + { + // Implementation of writing a partition with SecureBoot variable to the phone + ModeSwitchProgressWrapper(ProgressText, MassStorageWarning); + LogFile.Log("Preparing phone for Mass Storage Mode", LogType.FileAndConsole); + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + + // Magic! + // The SBMSM resource is a compressed version of a raw NV-variable-partition. + // In this partition the SecureBoot variable is disabled and an extra variable is added which triggers Mass Storage Mode on next reboot. + // It overwrites the variable in a different NV-partition than where this variable is stored usually. + // This normally leads to endless-loops when the NV-variables are enumerated. + // But the partition contains an extra hack to break out the endless loops. + using (var stream = assembly.GetManifestResourceStream("WPinternals.SBMSM")) + { + using DecompressedStream dec = new(stream); + using MemoryStream SB = new(); // Must be a seekable stream! + dec.CopyTo(SB); + + // We don't need to check for the BACKUP_BS_NV partition here, + // because the SecureBoot flag is disabled here. + // So either the NV was already backupped or already overwritten. + + GPT GPT = FlashModel.ReadGPT(); + Partition Target = GPT.GetPartition("UEFI_BS_NV"); + + // We've been reading the GPT, so we let the phone reset once more to be sure that memory maps are the same + WPinternalsStatus LastStatus = WPinternalsStatus.Undefined; + List Parts = new(); + FlashPart Part = new(); + Part.StartSector = (uint)Target.FirstSector; + Part.Stream = SB; + Parts.Add(Part); + await LumiaV2UnlockBootViewModel.LumiaV2CustomFlash(PhoneNotifier, null, false, false, Parts, DoResetFirst: true, ClearFlashingStatusAtEnd: false, ShowProgress: false, + SetWorkingStatus: (m, s, v, a, st) => + { + if (SetWorkingStatus != null) + { + if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) + { + SetWorkingStatus(m, s, v, a, st); + } + else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) + { + SetWorkingStatus(ProgressText, MassStorageWarning); + } + + LastStatus = st; + } + }, + UpdateWorkingStatus: (m, s, v, st) => + { + if (UpdateWorkingStatus != null) + { + if ((st == WPinternalsStatus.Scanning) || (st == WPinternalsStatus.WaitingForManualReset)) + { + UpdateWorkingStatus(m, s, v, st); + } + else if ((LastStatus == WPinternalsStatus.Scanning) || (LastStatus == WPinternalsStatus.WaitingForManualReset)) + { + SetWorkingStatus(ProgressText, MassStorageWarning); + } + + LastStatus = st; + } + }); + } + + if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_BadMassStorage) + { + throw new WPinternalsException("Phone is in Mass Storage mode, but the driver on PC failed to start"); + } + + // Wait for bootloader + if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_MassStorage) + { + LogFile.Log("Waiting for Mass Storage Mode (1)...", LogType.FileOnly); + await PhoneNotifier.WaitForArrival(); + } + + if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_BadMassStorage) + { + throw new WPinternalsException("Phone is in Mass Storage mode, but the driver on PC failed to start"); + } + + // Wait for mass storage mode + if (PhoneNotifier.CurrentInterface != PhoneInterfaces.Lumia_MassStorage) + { + LogFile.Log("Waiting for Mass Storage Mode (2)...", LogType.FileOnly); + await PhoneNotifier.WaitForArrival(); + } + + if (PhoneNotifier.CurrentInterface == PhoneInterfaces.Lumia_BadMassStorage) + { + throw new WPinternalsException("Phone is in Mass Storage mode, but the driver on PC failed to start"); + } + + MassStorage Storage = null; + if (PhoneNotifier.CurrentModel is MassStorage) + { + Storage = (MassStorage)PhoneNotifier.CurrentModel; + } + + if (Storage == null) + { + ModeSwitchErrorWrapper("Failed to switch to Mass Storage Mode"); + } + else + { + ModeSwitchSuccessWrapper(); + } + } + catch (Exception Ex) + { + LogFile.LogException(Ex); + ModeSwitchErrorWrapper(Ex.Message); + } + + LogFile.EndAction("SwitchToMassStorageMode"); + }).Start(); + } + else + { + ModeSwitchErrorWrapper("Bootloader was not unlocked. First unlock bootloader before you try to switch to Mass Storage Mode."); + } + } + + internal async static Task SwitchTo(PhoneNotifierViewModel Notifier, PhoneInterfaces? TargetMode, string RequestedVolumeForMassStorage = "MainOS") + { + return await SwitchToWithProgress(Notifier, TargetMode, null, RequestedVolumeForMassStorage); + } + + internal async static Task SwitchToWithProgress(PhoneNotifierViewModel Notifier, PhoneInterfaces? TargetMode, ModeSwitchProgressHandler ModeSwitchProgress, string RequestedVolumeForMassStorage = "MainOS") + { + if (Notifier.CurrentInterface == TargetMode) + { + return Notifier.CurrentModel; + } + + IDisposable Result = null; + string LocalErrorMessage = null; + + AsyncAutoResetEvent Event = new(false); + + SwitchModeViewModel Switch = new( + Notifier, + TargetMode, + ModeSwitchProgress, + (string ErrorMessage) => + { + LocalErrorMessage = ErrorMessage; + Event.Set(); + }, + (IDisposable NewModel, PhoneInterfaces NewInterface) => + { + Result = NewModel; + Event.Set(); + } + ); + + await Event.WaitAsync(Timeout.InfiniteTimeSpan); + + if (LocalErrorMessage != null) + { + throw new WPinternalsException(LocalErrorMessage); + } + + return Result; + } + + internal async static Task SwitchToWithStatus(PhoneNotifierViewModel Notifier, PhoneInterfaces? TargetMode, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, string RequestedVolumeForMassStorage = "MainOS") + { + if (Notifier.CurrentInterface == TargetMode) + { + return Notifier.CurrentModel; + } + + IDisposable Result = null; + string LocalErrorMessage = null; + + AsyncAutoResetEvent Event = new(false); + + SwitchModeViewModel Switch = new( + Notifier, + TargetMode, + null, + (string ErrorMessage) => + { + LocalErrorMessage = ErrorMessage; + Event.Set(); + }, + (IDisposable NewModel, PhoneInterfaces NewInterface) => + { + Result = NewModel; + Event.Set(); + }, SetWorkingStatus, UpdateWorkingStatus + ); + + await Event.WaitAsync(Timeout.InfiniteTimeSpan); + + if (LocalErrorMessage != null) + { + throw new WPinternalsException(LocalErrorMessage); + } + + return Result; + } + } +} diff --git a/Views/About.xaml b/WPinternals/Views/About.xaml similarity index 98% rename from Views/About.xaml rename to WPinternals/Views/About.xaml index 70389c2..57f09e1 100644 --- a/Views/About.xaml +++ b/WPinternals/Views/About.xaml @@ -1,94 +1,94 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - license - - - - license - - - - license - - - - license - - - - license - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + license + + + + license + + + + license + + + + license + + + + license + + + + + + + diff --git a/Views/About.xaml.cs b/WPinternals/Views/About.xaml.cs similarity index 97% rename from Views/About.xaml.cs rename to WPinternals/Views/About.xaml.cs index 7d525ba..d3b5bef 100644 --- a/Views/About.xaml.cs +++ b/WPinternals/Views/About.xaml.cs @@ -1,54 +1,54 @@ -// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; - -namespace WPinternals -{ - /// - /// Interaction logic for About.xaml - /// - public partial class About : UserControl - { - public About() - { - InitializeComponent(); - } - - private void HandleHyperlinkClick(object sender, RoutedEventArgs args) - { - if (args.Source is Hyperlink link) - { - Process process = new(); - process.StartInfo.UseShellExecute = true; - process.StartInfo.FileName = link.NavigateUri.AbsoluteUri; - process.Start(); - } - } - - private void Document_Loaded(object sender, RoutedEventArgs e) - { - (sender as FlowDocument)?.AddHandler(Hyperlink.ClickEvent, new RoutedEventHandler(HandleHyperlinkClick)); - } - } -} +// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; + +namespace WPinternals +{ + /// + /// Interaction logic for About.xaml + /// + public partial class About : UserControl + { + public About() + { + InitializeComponent(); + } + + private void HandleHyperlinkClick(object sender, RoutedEventArgs args) + { + if (args.Source is Hyperlink link) + { + Process process = new(); + process.StartInfo.UseShellExecute = true; + process.StartInfo.FileName = link.NavigateUri.AbsoluteUri; + process.Start(); + } + } + + private void Document_Loaded(object sender, RoutedEventArgs e) + { + (sender as FlowDocument)?.AddHandler(Hyperlink.ClickEvent, new RoutedEventHandler(HandleHyperlinkClick)); + } + } +} diff --git a/Views/BackupView.xaml b/WPinternals/Views/BackupView.xaml similarity index 98% rename from Views/BackupView.xaml rename to WPinternals/Views/BackupView.xaml index 5fc703b..fdc8bb4 100644 --- a/Views/BackupView.xaml +++ b/WPinternals/Views/BackupView.xaml @@ -1,148 +1,148 @@ - - - - - - - - - - - - - - - - - - - - - - unlocked - - - - - - - - - - - - - -