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

                            if (RootKeyHash == null)
                            {
                                RootKeyHash = new byte[32];
                            }
                        }

                        TestPos = 3;

                        if (Info.FlashAppProtocolVersionMajor < 2)
                        {
                            // This action is executed after the resources are selected by the user.
                            Action <string, string, string, string, string, string, bool> ReturnFunction = (FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, SupportedFFUPath, 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.
                            Action <string, string, string, string, string, string, bool> ReturnFunction = (FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, SupportedFFUPath, 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 <FFUEntry> FFUs = App.Config.FFURepository.Where(e => (Info.PlatformID.StartsWith(e.PlatformID, StringComparison.OrdinalIgnoreCase) && e.Exists())).ToList();
                                        if (FFUs.Count() > 0)
                                        {
                                            ProfileFFU = new FFU(FFUs[0].Path);
                                        }
                                        else
                                        {
                                            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 QualcommDownload((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 <string, string, string, string, string, string, bool> 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);
                    if (DoUnlock)
                    {
                        SBL3Path = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "SBL3Path", null);
                    }
                    else
                    {
                        SBL3Path = 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;
                }
            }
        }
        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 GPT(GPTChunk);
                    FlashPart Part;
                    List <FlashPart> FlashParts = new List <FlashPart>();

                    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();
                        Part.StartSector = 0;
                        Part.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 FFU(FFUPath);
                    BusyViewModel Busy = new BusyViewModel("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();
        }
예제 #3
0
        // V3 exploit
        // Magic
        //
        internal async static Task LumiaV3CustomFlash(PhoneNotifierViewModel Notifier, List <FlashPart> FlashParts, bool CheckSectorAlignment = true, 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) => { }
            }
            ;

            uint chunkSize  = 131072u;
            int  chunkSizes = 131072;

            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) % chunkSize) != 0)
                    {
                        throw new ArgumentException("Invalid StartSector alignment");
                    }
                    if (CheckSectorAlignment)
                    {
                        if ((Part.Stream.Length % chunkSize) != 0)
                        {
                            throw new ArgumentException("Invalid Data length");
                        }
                    }
                }
            }

            try
            {
                NokiaFlashModel Model = (NokiaFlashModel)Notifier.CurrentModel;
                PhoneInfo       Info  = Model.ReadPhoneInfo();

                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 + ".");
                }

                if (Info.UefiSecureBootEnabled)
                {
                    throw new WPinternalsException("Flash failed!", "UEFI Secureboot must be disabled for the Flash V3 exploit to work.");
                }

                // 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
                //
                LumiaV2UnlockBootViewModel.FlashingPayload[] payloads = new LumiaV2UnlockBootViewModel.FlashingPayload[0];
                if (FlashParts != null)
                {
                    payloads = LumiaV2UnlockBootViewModel.GetNonOptimizedPayloads(FlashParts, chunkSizes, (uint)(Info.WriteBufferSize / chunkSize), SetWorkingStatus, UpdateWorkingStatus).OrderBy(x => x.TargetLocations.Count()).ToArray();
                }

                MemoryStream Headerstream1 = new MemoryStream();

                // ==============================
                // Header 1 start

                ImageHeader image   = new ImageHeader();
                FullFlash   ffimage = new FullFlash();
                Store       simage  = new Store();

                // Todo make this read the image itself
                ffimage.OSVersion         = "10.0.11111.0";
                ffimage.DevicePlatformId0 = Info.PlatformID;
                ffimage.AntiTheftVersion  = "1.1";

                simage.SectorSize     = 512;
                simage.MinSectorCount = Info.EmmcSizeInSectors;

                //Logging.Log("Generating image manifest...");
                string manifest = ManifestIni.BuildUpManifest(ffimage, simage);//, partitions);

                byte[] TextBytes = System.Text.Encoding.ASCII.GetBytes(manifest);

                image.ManifestLength = (UInt32)TextBytes.Length;

                byte[] ImageHeaderBuffer = new byte[0x18];

                ByteOperations.WriteUInt32(ImageHeaderBuffer, 0, image.Size);
                ByteOperations.WriteAsciiString(ImageHeaderBuffer, 0x04, image.Signature);
                ByteOperations.WriteUInt32(ImageHeaderBuffer, 0x10, image.ManifestLength);
                ByteOperations.WriteUInt32(ImageHeaderBuffer, 0x14, image.ChunkSize);

                Headerstream1.Write(ImageHeaderBuffer, 0, 0x18);
                Headerstream1.Write(TextBytes, 0, TextBytes.Length);

                RoundUpToChunks(Headerstream1, chunkSize);

                // Header 1 stop + round
                // ==============================

                MemoryStream Headerstream2 = new MemoryStream();

                // ==============================
                // Header 2 start

                StoreHeader store = new StoreHeader();

                store.WriteDescriptorCount = (UInt32)payloads.Count();
                store.FinalTableIndex      = (UInt32)payloads.Count() - store.FinalTableCount;
                store.PlatformId           = Info.PlatformID;

                foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
                {
                    store.WriteDescriptorLength += payload.GetStoreHeaderSize();
                }

                byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Model, 0x20000);
                GPT    GPT      = new GPT(GPTChunk);
                UInt64 PlatEnd  = 0;
                if (GPT.Partitions.Any(x => x.Name == "PLAT"))
                {
                    PlatEnd = GPT.GetPartition("PLAT").LastSector;
                }

                foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
                {
                    if (payload.TargetLocations.First() > PlatEnd)
                    {
                        break;
                    }
                    store.FlashOnlyTableIndex += 1;
                }

                byte[] StoreHeaderBuffer = new byte[0xF8];
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0, store.UpdateType);
                ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x04, store.MajorVersion);
                ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x06, store.MinorVersion);
                ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x08, store.FullFlashMajorVersion);
                ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x0A, store.FullFlashMinorVersion);
                ByteOperations.WriteAsciiString(StoreHeaderBuffer, 0x0C, store.PlatformId);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xCC, store.BlockSizeInBytes);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD0, store.WriteDescriptorCount);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD4, store.WriteDescriptorLength);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD8, store.ValidateDescriptorCount);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xDC, store.ValidateDescriptorLength);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE0, store.InitialTableIndex);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE4, store.InitialTableCount);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE8, store.FlashOnlyTableIndex);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xEC, store.FlashOnlyTableCount);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xF0, store.FinalTableIndex);
                ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xF4, store.FinalTableCount);
                Headerstream2.Write(StoreHeaderBuffer, 0, 0xF8);

                byte[] descriptorsBuffer = new byte[store.WriteDescriptorLength];

                UInt32 NewWriteDescriptorOffset = 0;
                foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
                {
                    ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x00, (UInt32)payload.TargetLocations.Count()); // Location count
                    ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x04, payload.ChunkCount);                      // Chunk count
                    NewWriteDescriptorOffset += 0x08;

                    foreach (UInt32 location in payload.TargetLocations)
                    {
                        ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x00, 0x00000000);                          // Disk access method (0 = Begin, 2 = End)
                        ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x04, location);                            // Chunk index
                        NewWriteDescriptorOffset += 0x08;
                    }
                }

                Headerstream2.Write(descriptorsBuffer, 0, (Int32)store.WriteDescriptorLength);

                RoundUpToChunks(Headerstream2, chunkSize);

                // Header 2 stop + round
                // ==============================

                SecurityHeader security = new SecurityHeader();

                Headerstream1.Seek(0, SeekOrigin.Begin);
                Headerstream2.Seek(0, SeekOrigin.Begin);

                security.HashTableSize = 0x20 * (UInt32)((Headerstream1.Length + Headerstream2.Length) / chunkSize);

                foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
                {
                    security.HashTableSize += payload.GetSecurityHeaderSize();
                }

                byte[]       HashTable = new byte[security.HashTableSize];
                BinaryWriter bw        = new BinaryWriter(new MemoryStream(HashTable));

                SHA256 crypto = SHA256.Create();
                for (Int32 i = 0; i < Headerstream1.Length / chunkSize; i++)
                {
                    byte[] buffer = new byte[chunkSize];
                    Headerstream1.Read(buffer, 0, (Int32)chunkSize);
                    byte[] hash = crypto.ComputeHash(buffer);
                    bw.Write(hash, 0, hash.Length);
                }

                for (Int32 i = 0; i < Headerstream2.Length / chunkSize; i++)
                {
                    byte[] buffer = new byte[chunkSize];
                    Headerstream2.Read(buffer, 0, (Int32)chunkSize);
                    byte[] hash = crypto.ComputeHash(buffer);
                    bw.Write(hash, 0, hash.Length);
                }

                foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
                {
                    bw.Write(payload.ChunkHashes[0], 0, payload.ChunkHashes[0].Length);
                }

                bw.Close();

                //Logging.Log("Generating image catalog...");
                byte[] catalog = GenerateCatalogFile(HashTable);

                security.CatalogSize = (UInt32)catalog.Length;

                byte[] SecurityHeaderBuffer = new byte[0x20];

                ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0, security.Size);
                ByteOperations.WriteAsciiString(SecurityHeaderBuffer, 0x04, security.Signature);
                ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x10, security.ChunkSizeInKb);
                ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x14, security.HashAlgorithm);
                ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x18, security.CatalogSize);
                ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x1C, security.HashTableSize);

                MemoryStream retstream = new MemoryStream();

                retstream.Write(SecurityHeaderBuffer, 0, 0x20);

                retstream.Write(catalog, 0, (Int32)security.CatalogSize);
                retstream.Write(HashTable, 0, (Int32)security.HashTableSize);

                RoundUpToChunks(retstream, chunkSize);

                Headerstream1.Seek(0, SeekOrigin.Begin);
                Headerstream2.Seek(0, SeekOrigin.Begin);

                byte[] buff = new byte[Headerstream1.Length];
                Headerstream1.Read(buff, 0, (Int32)Headerstream1.Length);

                Headerstream1.Close();

                retstream.Write(buff, 0, buff.Length);

                buff = new byte[Headerstream2.Length];
                Headerstream2.Read(buff, 0, (Int32)Headerstream2.Length);

                Headerstream2.Close();

                retstream.Write(buff, 0, buff.Length);

                // --------

                // Go back to the beginning
                retstream.Seek(0, SeekOrigin.Begin);

                byte[] FfuHeader = new byte[retstream.Length];
                await retstream.ReadAsync(FfuHeader, 0, (Int32)retstream.Length);

                retstream.Close();

                Byte Options = 0;
                if (!Info.IsBootloaderSecure)
                {
                    Options = (Byte)((FlashOptions)Options | FlashOptions.SkipSignatureCheck);
                }

                LogFile.Log("Flash in progress...", LogType.ConsoleOnly);
                SetWorkingStatus("Flashing...", null, (UInt64?)payloads.Count(), Status: WPinternalsStatus.Flashing);

                Model.SendFfuHeaderV1(FfuHeader, Options);

                UInt64 counter = 0;
                Int32  numberOfPayloadsToSendAtOnce = 1;
                if ((Info.SecureFfuSupportedProtocolMask & (UInt16)FfuProtocol.ProtocolSyncV2) != 0)
                {
                    numberOfPayloadsToSendAtOnce = (Int32)Math.Round((Double)Info.WriteBufferSize / chunkSize);
                }

                byte[] payloadBuffer;

                for (Int32 i = 0; i < payloads.Count(); i += numberOfPayloadsToSendAtOnce)
                {
                    if (i + numberOfPayloadsToSendAtOnce - 1 >= payloads.Count())
                    {
                        numberOfPayloadsToSendAtOnce = payloads.Count() - i;
                    }

                    payloadBuffer = new byte[numberOfPayloadsToSendAtOnce * chunkSizes];

                    string ProgressText = "Flashing resources";

                    for (Int32 j = 0; j < numberOfPayloadsToSendAtOnce; j++)
                    {
                        LumiaV2UnlockBootViewModel.FlashingPayload payload = payloads[i + j];

                        UInt32    StreamIndex = payload.StreamIndexes.First();
                        FlashPart flashPart   = FlashParts[(Int32)StreamIndex];

                        if (flashPart.ProgressText != null)
                        {
                            ProgressText = flashPart.ProgressText;
                        }

                        Stream Stream = flashPart.Stream;
                        Stream.Seek(payload.StreamLocations.First(), SeekOrigin.Begin);
                        Stream.Read(payloadBuffer, j * chunkSizes, chunkSizes);
                        counter++;
                    }

                    if ((Info.SecureFfuSupportedProtocolMask & (ushort)FfuProtocol.ProtocolSyncV2) != 0)
                    {
                        Model.SendFfuPayloadV2(payloadBuffer, Int32.Parse((counter * 100 / (UInt64)payloads.Count()).ToString()));
                    }
                    else
                    {
                        Model.SendFfuPayloadV1(payloadBuffer, Int32.Parse((counter * 100 / (UInt64)payloads.Count()).ToString()));
                    }

                    UpdateWorkingStatus(ProgressText, null, counter, WPinternalsStatus.Flashing);
                }

                Model.ResetPhone();

                await Notifier.WaitForRemoval();

                ExitSuccess("Flash succeeded!", null);
            }
            catch
            {
                throw new WPinternalsException("Custom flash failed");
            }
        }
    }
}