// Copyright (c) 2018, Rene Lergner - wpinternals.net - @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.IO; using System.Linq; using System.Threading; 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, LumiaSpecAGetGPT, LumiaSpecBUnlockBoot }; internal class LumiaUnlockBootViewModel : ContextViewModel { private PhoneNotifierViewModel PhoneNotifier; private Action Callback; private bool IsFlashingDone = false; 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 List PossibleLoaders; private GPT NewGPT; private byte[] GPT; private byte[] ExtraSector; private byte[] SBL2; private byte[] SBL3; private byte[] UEFI; private FFU FFU; private Action SwitchToFlashRom; private Action SwitchToUndoRoot; private Action SwitchToDownload; private bool DoUnlock; private MachineState State; private object EvaluateViewStateLockObject = new object(); 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; } internal void SendLoader() { // Assume 9008 mode if (!((PhoneNotifier.CurrentModel is QualcommSerial) && (PossibleLoaders != null) && (PossibleLoaders.Count > 0))) return; ActivateSubContext(new BusyViewModel("Sending loader...")); LogFile.Log("Sending loader"); QualcommSerial Serial = (QualcommSerial)PhoneNotifier.CurrentModel; QualcommDownload Download = new QualcommDownload(Serial); if (Download.IsAlive()) { int Attempt = 1; bool Result = false; foreach (QualcommPartition Loader in PossibleLoaders) { LogFile.Log("Attempt " + Attempt.ToString()); try { Download.SendToPhoneMemory(0x2A000000, Loader.Binary); Download.StartBootloader(0x2A000000); Result = true; LogFile.Log("Loader sent successfully"); } catch { } if (Result) break; Attempt++; } Serial.Close(); if (!Result) LogFile.Log("Loader failed"); } else { LogFile.Log("Failed to communicate to Qualcomm Emergency Download mode"); throw new BadConnectionException(); } } // Potentially blocking UI. Threadsafe. internal override void EvaluateViewState() { if (!IsActive) return; if ((State == MachineState.LumiaSpecAGetGPT) || (State == MachineState.LumiaSpecBUnlockBoot)) return; lock (EvaluateViewStateLockObject) { switch (PhoneNotifier.CurrentInterface) { case PhoneInterfaces.Lumia_Normal: case PhoneInterfaces.Lumia_Label: IsSwitchingInterface = false; if (IsFlashingDone) { if (DoUnlock) { LogFile.Log("Bootloader successfully unlocked!"); ActivateSubContext(new MessageViewModel("Bootloader succesfully unlocked!", Exit)); } else { LogFile.Log("Bootloader successfully restored!"); ActivateSubContext(new MessageViewModel("Bootloader succesfully restored!", Exit)); } } else { 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. // Flash Param "FS" is the Flash Status (4 bytes, Big Endian DWORD, values: 0 / 1) // When flashing is done and phone is still in flash-mode, write raw dummy sector and restart phone NokiaFlashModel FlashModel = (NokiaFlashModel)PhoneNotifier.CurrentModel; if (IsFlashingDone && (FlashModel.ReadParam("FS")[3] > 0)) { if (DoUnlock) { IsSwitchingInterface = true; LogFile.Log("Phone detected in Flash-in-progress-mode. Escaping from Flash-mode.;"); ActivateSubContext(new BusyViewModel("Escaping from Flash mode...")); new Thread(() => { RecoverFromFlashMode(); }).Start(); } else { IsSwitchingInterface = false; ActivateSubContext(new MessageViewModel("Bootloader restored. You can now flash a stock ROM.", SwitchToFlashRom)); } } else { 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(); IsBootLoaderUnlocked = (SecurityStatus.AuthenticationStatus || SecurityStatus.RdcStatus || !SecurityStatus.SecureFfuEfuseStatus); TestPos = 2; PhoneInfo Info = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadPhoneInfo(); TestPos = 3; if (Info.FlashAppProtocolVersionMajor < 2) { // This action is executed after the resources are selected by the user. Action ReturnFunction = (FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, SupportedFFUPath, DoFixBoot) => { // 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...")); new Thread(() => { bool ResourcesVerified = false; try { ResourcesVerified = EvaluateResources(); if (!ResourcesVerified) { LogFile.Log("Processing resources failed."); ActivateSubContext(new MessageViewModel("Invalid resources.", Abort)); } } catch (Exception Ex) { LogFile.LogException(Ex); ActivateSubContext(new MessageViewModel(Ex.Message, Abort)); } if (ResourcesVerified) { if (IsBootLoaderUnlocked) { CustomFlashBootLoader(); } else PerformSoftBrick(); } }).Start(); }; 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 { if (DoUnlock) { GPT GPT = FlashModel.ReadGPT(); if ((GPT.GetPartition("IS_UNLOCKED") != null) || (GPT.GetPartition("BACKUP_EFIESP") != null)) { ExitMessage("Phone is already unlocked", null); return; } } 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 ReturnFunction = (FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, SupportedFFUPath, DoFixBoot) => { State = MachineState.LumiaSpecBUnlockBoot; 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 await LumiaV2UnlockBootViewModel.LumiaV2UnlockBootloader(PhoneNotifier, ProfileFFUPath, EDEPath, SupportedFFUPath, SetWorkingStatus, UpdateWorkingStatus, ExitMessage, ExitMessage); }); } else { Task.Run(async () => { await LumiaV2UnlockBootViewModel.LumiaV2RelockPhone(PhoneNotifier, null, 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 = Download.GetRKH(); 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; } if ((this.FFUPath == null) || (this.PossibleLoaders == null) || (this.PossibleLoaders.Count == 0)) { // This action is executed after the user selected the resources. Action ReturnFunction = (FFUPath, LoadersPath, SBL3Path, ProfileFFUPath, EDEPath, SupportedFFUPath, DoFixBoot) => { // 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...")); new Thread(() => { bool ResourcesVerified = false; try { ResourcesVerified = EvaluateResources(); if (!ResourcesVerified) { LogFile.Log("Processing resources failed."); ActivateSubContext(new MessageViewModel("Invalid resources.", Abort)); } } catch (Exception Ex) { LogFile.LogException(Ex); ActivateSubContext(new MessageViewModel(Ex.Message, Abort)); } if (ResourcesVerified) SendLoader(); }).Start(); }; if (DoUnlock) ActivateSubContext(new BootUnlockResourcesViewModel("Qualcomm Emergency Download mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, false)); else ActivateSubContext(new BootRestoreResourcesViewModel("Qualcomm Emergency Download mode", RootKeyHash, SwitchToFlashRom, SwitchToUndoRoot, SwitchToDownload, ReturnFunction, Abort, IsBootLoaderUnlocked, false)); } else new Thread(() => { SendLoader(); }).Start(); break; case PhoneInterfaces.Qualcomm_Flash: new Thread(() => { FlashBootLoader(); }).Start(); break; case PhoneInterfaces.Lumia_Bootloader: IsSwitchingInterface = true; if (IsFlashingDone) { LogFile.Log("Booting phone"); ActivateSubContext(new BusyViewModel("Booting phone...")); } 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); if (Key == null) Key = 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); } NokiaFlashModel Model = (NokiaFlashModel)PhoneNotifier.CurrentModel; PhoneInfo Info = Model.ReadPhoneInfo(); if (ProfileFFUPath == null) { if (Key.GetValue("ProfileFFUPath") != null) Key.DeleteValue("ProfileFFUPath"); } else { Key.SetValue("ProfileFFUPath", ProfileFFUPath); App.Config.AddFfuToRepository(ProfileFFUPath); } 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 IsFlashingDone = false; FFUPath = null; LoadersPath = null; PossibleLoaders = 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 MessageViewModel(Message, () => { State = MachineState.Default; Exit(); }); SuccessMessageViewModel.SubMessage = SubMessage; ActivateSubContext(SuccessMessageViewModel); } // Potentially blocking UI. Threadsafe. private bool EvaluateResources(bool Emergency = false) { bool Result = true; bool DumpPartitions = false; string DumpFilePrefix = Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%\\WPInternals\\") + DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss") + " - "; if ((FFUPath == null) || (FFUPath.Length == 0)) throw new Exception("Error: Path for FFU-file is mandatory."); if (DoUnlock && ((LoadersPath == null) || (LoadersPath.Length == 0))) throw new Exception("Error: Path for Loaders is mandatory."); if (PhoneNotifier.CurrentModel is NokiaFlashModel) { FlashVersion FlashVersion = ((NokiaFlashModel)PhoneNotifier.CurrentModel).GetFlashVersion(); if (FlashVersion == null) throw new Exception("Error: The version of the Flash Application on the phone could not be determined."); if ((FlashVersion.ApplicationMajor < 1) || ((FlashVersion.ApplicationMajor == 1) && (FlashVersion.ApplicationMinor < 28))) throw new Exception("Error: The version of the Flash Application on the phone is too old. Update your phone using Windows Updates or flash a newer ROM to your phone. Then try again."); } try { FFU = new FFU(FFUPath); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Parsing FFU-file failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "01.bin", FFU.GetSectors(0, 34)); // Original GPT } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } State = MachineState.LumiaSpecAGetGPT; // Stop handling arrival notifications in this screen IsSwitchingInterface = true; // Stop handling arrival notifications in MainViewModel SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Lumia_Bootloader).Wait(); NewGPT = ((NokiaFlashModel)PhoneNotifier.CurrentModel).ReadGPT(); SwitchModeViewModel.SwitchTo(PhoneNotifier, PhoneInterfaces.Lumia_Flash).Wait(); IsSwitchingInterface = true; State = MachineState.Default; // Make sure all partitions are in range of the emergency flasher. NewGPT.RestoreBackupPartitions(); // Magic! // SecureBoot hack for Bootloader Spec A if (DoUnlock) { try { this.GPT = NewGPT.InsertHack(); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Parsing partitions failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "02.bin", this.GPT); // Patched GPT } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } } else { NewGPT.RemoveHack(); this.GPT = NewGPT.Rebuild(); } SBL1 SBL1 = null; try { SBL1 = new SBL1(FFU.GetPartition("SBL1")); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Parsing SBL1 failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "03.bin", SBL1.Binary); // Original SBL1 } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } if (!Emergency) { if (RootKeyHash == null) { Result = false; throw new Exception("Error: Root Key Hash could not be retrieved from the phone."); } if (SBL1.RootKeyHash == null) { Result = false; throw new Exception("Error: Root Key Hash could not be retrieved from FFU file."); } if (!StructuralComparisons.StructuralEqualityComparer.Equals(RootKeyHash, SBL1.RootKeyHash)) { LogFile.Log("Phone: " + Converter.ConvertHexToString(RootKeyHash, "")); LogFile.Log("SBL1: " + Converter.ConvertHexToString(SBL1.RootKeyHash, "")); Result = false; throw new Exception("Error: Root Key Hash from phone and from FFU file do not match!"); } } SBL2 SBL2 = null; try { SBL2 = new SBL2(FFU.GetPartition("SBL2")); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Parsing SBL2 failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "05.bin", SBL2.Binary); // Original SBL2 } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } if (DoUnlock) { try { this.SBL2 = SBL2.Patch(); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Patching SBL2 failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "06.bin", SBL2.Binary); // Patched SBL2 } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } } else { this.SBL2 = SBL2.Binary; } this.ExtraSector = null; if (DoUnlock) { try { byte[] PartitionHeader = new byte[0x0C]; Buffer.BlockCopy(SBL2.Binary, 0, PartitionHeader, 0, 0x0C); this.ExtraSector = SBL1.GenerateExtraSector(PartitionHeader); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Code generation failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "04.bin", this.ExtraSector); // Extra sector } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } } SBL3 SBL3; SBL3 OriginalSBL3; try { OriginalSBL3 = new SBL3(FFU.GetPartition("SBL3")); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Parsing SBL3 from FFU failed."); } if (SBL3Path == null) { SBL3 = OriginalSBL3; LogFile.Log("Taking SBL3 from FFU"); } else { SBL3 = null; try { SBL3 = new SBL3(SBL3Path); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Parsing external SBL3 failed."); } if (SBL3.Binary.Length > OriginalSBL3.Binary.Length) { throw new Exception("Error: Selected SBL3 is too large."); } LogFile.Log("Taking selected SBL3"); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "07.bin", SBL3.Binary); // Original SBL3 } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } if (DoUnlock) { try { this.SBL3 = SBL3.Patch(); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Patching SBL3 failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "08.bin", SBL3.Binary); // Patched SBL3 } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } } else { this.SBL3 = SBL3.Binary; } UEFI UEFI = null; try { UEFI = new UEFI(FFU.GetPartition("UEFI")); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Parsing UEFI failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "09.bin", UEFI.Binary); // Original UEFI } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } if (DoUnlock) { try { this.UEFI = UEFI.Patch(); } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Patching UEFI failed."); } if (DumpPartitions) { try { File.WriteAllBytes(DumpFilePrefix + "0A.bin", UEFI.Binary); // Patched UEFI } catch (Exception Ex) { LogFile.LogException(Ex); throw new Exception("Error: Writing binary for logging failed."); } } } else { this.UEFI = UEFI.Binary; } if (!IsBootLoaderUnlocked) { try { PossibleLoaders = QualcommLoaders.GetPossibleLoadersForRootKeyHash(LoadersPath, this.RootKeyHash); if (PossibleLoaders.Count == 0) { Result = false; throw new Exception("Error: No matching loaders found for RootKeyHash."); } } catch (Exception Ex) { LogFile.LogException(Ex); Result = false; throw new Exception("Error: Unexpected error during scanning for loaders."); } } return Result; } // TODO: Add logging private void PerformSoftBrick() { IsSwitchingInterface = true; ActivateSubContext(new BusyViewModel("Switching to Emergency Download mode...")); // Send FFU headers UInt64 CombinedFFUHeaderSize = this.FFU.HeaderSize; byte[] FfuHeader = new byte[CombinedFFUHeaderSize]; System.IO.FileStream FfuFile = new System.IO.FileStream(FFUPath, System.IO.FileMode.Open, System.IO.FileAccess.Read); FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); FfuFile.Close(); try { ((NokiaFlashModel)PhoneNotifier.CurrentModel).SendFfuHeaderV1(FfuHeader); } catch (Exception Ex) { IsSwitchingInterface = false; LogFile.LogException(Ex); ActivateSubContext(new MessageViewModel("Error using FFU. Try an FFU image which matches the phone.", Abort)); return; } // Send 1 empty chunk (according to layout in FFU headers, it will be written to first and last chunk) byte[] EmptyChunk = new byte[0x20000]; Array.Clear(EmptyChunk, 0, 0x20000); ((NokiaFlashModel)PhoneNotifier.CurrentModel).SendFfuPayloadV1(EmptyChunk); // Reboot to Qualcomm Emergency mode byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; // NOKR ((NokiaFlashModel)PhoneNotifier.CurrentModel).ExecuteRawVoidMethod(RebootCommand); } 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(); } private void RecoverFromFlashMode() { IsSwitchingInterface = true; LogFile.Log("Recover from Flash-mode"); // Flash dummy sector (only allowed when phone is authenticated) byte[] EmptySector = new byte[0x200]; Array.Clear(EmptySector, 0, 0x200); ((NokiaFlashModel)PhoneNotifier.CurrentModel).FlashSectors(0x22, EmptySector); // Reboot to Qualcomm Emergency mode byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; // NOKR ((NokiaFlashModel)PhoneNotifier.CurrentModel).ExecuteRawVoidMethod(RebootCommand); } // Expected to be launched on worker-thread. internal void FlashBootLoader() { IsSwitchingInterface = true; LogFile.Log("Start flashing in Qualcomm Emergency Flash mode"); if (this.FFU == null) { 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."); // In case tool was terminated FFUPath = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\WPInternals", "FFUPath", 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; try { EvaluateResources(Emergency:true); } catch (Exception Ex) { LogFile.LogException(Ex); ActivateSubContext(new MessageViewModel(Ex.Message, Abort)); } } QualcommSerial Serial = (QualcommSerial)PhoneNotifier.CurrentModel; Serial.EncodeCommands = false; QualcommFlasher Flasher = new QualcommFlasher(Serial); UInt64 TotalSectorCount = (UInt64)1 + 0x21 + 1 + (UInt64)(SBL2.Length / 0x200) + (UInt64)(SBL3.Length / 0x200) + (UInt64)(UEFI.Length / 0x200) + NewGPT.GetPartition("SBL1").SizeInSectors - 1 + NewGPT.GetPartition("TZ").SizeInSectors + NewGPT.GetPartition("RPM").SizeInSectors + NewGPT.GetPartition("WINSECAPP").SizeInSectors; if (DoUnlock) ActivateSubContext(new BusyViewModel("Flashing unlocked bootloader...", MaxProgressValue: TotalSectorCount, UIContext: UIContext)); else ActivateSubContext(new BusyViewModel("Flashing original bootloader...", MaxProgressValue: TotalSectorCount, UIContext: UIContext)); ProgressUpdater Progress = ((BusyViewModel)SubContextViewModel).ProgressUpdater; Flasher.Hello(); Flasher.SetSecurityMode(0); Flasher.OpenPartition(0x21); LogFile.Log("Partition opened."); byte[] MBR = FFU.GetSectors(0, 1); if (ExtraSector != null) { LogFile.Log("Flash EXT at 0x" + ((UInt32)NewGPT.GetPartition("HACK").FirstSector * 0x200).ToString("X8")); Flasher.Flash((uint)NewGPT.GetPartition("HACK").FirstSector * 0x200, ExtraSector, Progress); } LogFile.Log("Flash MBR at 0x" + ((UInt32)0).ToString("X8")); Flasher.Flash(0, MBR, Progress, 0, 0x200); LogFile.Log("Flash GPT at 0x" + ((UInt32)0x200).ToString("X8")); Flasher.Flash(0x200, GPT, Progress, 0, 0x41FF); // Bad bounds-check in the flash-loader prohibits to write the last byte. LogFile.Log("Flash SBL2 at 0x" + ((UInt32)NewGPT.GetPartition("SBL2").FirstSector * 0x200).ToString("X8")); Flasher.Flash((uint)NewGPT.GetPartition("SBL2").FirstSector * 0x200, SBL2, Progress); LogFile.Log("Flash SBL3 at 0x" + ((UInt32)NewGPT.GetPartition("SBL3").FirstSector * 0x200).ToString("X8")); Flasher.Flash((uint)NewGPT.GetPartition("SBL3").FirstSector * 0x200, SBL3, Progress); LogFile.Log("Flash UEFI at 0x" + ((UInt32)NewGPT.GetPartition("UEFI").FirstSector * 0x200).ToString("X8")); Flasher.Flash((uint)NewGPT.GetPartition("UEFI").FirstSector * 0x200, UEFI, Progress); // To minimize risk of brick also flash these partitions: LogFile.Log("Flash SBL1 at 0x" + ((UInt32)NewGPT.GetPartition("SBL1").FirstSector * 0x200).ToString("X8")); Flasher.Flash((uint)NewGPT.GetPartition("SBL1").FirstSector * 0x200, FFU.GetPartition("SBL1"), Progress, 0, ((UInt32)NewGPT.GetPartition("SBL1").SizeInSectors - 1) * 0x200); LogFile.Log("Flash TZ at 0x" + ((UInt32)NewGPT.GetPartition("TZ").FirstSector * 0x200).ToString("X8")); Flasher.Flash((uint)NewGPT.GetPartition("TZ").FirstSector * 0x200, FFU.GetPartition("TZ"), Progress); LogFile.Log("Flash RPM at 0x" + ((UInt32)NewGPT.GetPartition("RPM").FirstSector * 0x200).ToString("X8")); Flasher.Flash((uint)NewGPT.GetPartition("RPM").FirstSector * 0x200, FFU.GetPartition("RPM"), Progress); // Workaround for bad bounds-check in flash-loader UInt32 Length = (UInt32)FFU.GetPartition("WINSECAPP").Length; UInt32 Start = (UInt32)NewGPT.GetPartition("WINSECAPP").FirstSector * 0x200; if ((Start + Length) > 0x1E7FE00) Length = 0x1E7FE00 - Start; LogFile.Log("Flash WINSECAPP at 0x" + ((UInt32)NewGPT.GetPartition("WINSECAPP").FirstSector * 0x200).ToString("X8")); Flasher.Flash(Start, FFU.GetPartition("WINSECAPP"), Progress, 0, Length); Flasher.ClosePartition(); IsFlashingDone = true; LogFile.Log("Partition closed. Flashing ready. Rebooting."); ActivateSubContext(new BusyViewModel("Flashing done. Rebooting...")); Flasher.Reboot(); Flasher.CloseSerial(); } // Expected to be launched on worker-thread. internal void CustomFlashBootLoader() { IsSwitchingInterface = true; LogFile.Log("Start flashing in Custom Flash mode"); NokiaFlashModel CurrentModel = (NokiaFlashModel)PhoneNotifier.CurrentModel; UInt64 TotalSectorCount = (UInt64)0x21 + 1 + (UInt64)(SBL2.Length / 0x200) + (UInt64)(SBL3.Length / 0x200) + (UInt64)(UEFI.Length / 0x200); if (DoUnlock) ActivateSubContext(new BusyViewModel("Flashing unlocked bootloader...", MaxProgressValue: TotalSectorCount, UIContext: UIContext)); else ActivateSubContext(new BusyViewModel("Flashing original bootloader...", MaxProgressValue: TotalSectorCount, UIContext: UIContext)); ProgressUpdater Progress = ((BusyViewModel)SubContextViewModel).ProgressUpdater; LogFile.Log("Flash GPT at 0x" + ((UInt32)0x200).ToString("X8")); CurrentModel.FlashSectors(1, GPT, 0); Progress.SetProgress(0x21); if (ExtraSector != null) { LogFile.Log("Flash EXT at 0x" + ((UInt32)NewGPT.GetPartition("HACK").FirstSector * 0x200).ToString("X8")); CurrentModel.FlashRawPartition(ExtraSector, "HACK", Progress); } LogFile.Log("Flash SBL2 at 0x" + ((UInt32)NewGPT.GetPartition("SBL2").FirstSector * 0x200).ToString("X8")); CurrentModel.FlashRawPartition(SBL2, "SBL2", Progress); LogFile.Log("Flash SBL3 at 0x" + ((UInt32)NewGPT.GetPartition("SBL3").FirstSector * 0x200).ToString("X8")); CurrentModel.FlashRawPartition(SBL3, "SBL3", Progress); LogFile.Log("Flash UEFI at 0x" + ((UInt32)NewGPT.GetPartition("UEFI").FirstSector * 0x200).ToString("X8")); CurrentModel.FlashRawPartition(UEFI, "UEFI", Progress); IsFlashingDone = true; ActivateSubContext(new BusyViewModel("Flashing done. Rebooting...")); byte[] RebootCommand = new byte[] { 0x4E, 0x4F, 0x4B, 0x52 }; // NOKR CurrentModel.ExecuteRawVoidMethod(RebootCommand); } } 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 string PlatformID; private 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 || ((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); 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 ((this is BootRestoreResourcesViewModel) || (_ProfileFFUPath == null)) { IsSupportedFfuNeeded = false; return; } try { FFU ProfileFFU = new FFU(_ProfileFFUPath); IsSupportedFfuNeeded = !(App.PatchEngine.PatchDefinitions.Where(p => p.Name == "SecureBootHack-V2-EFIESP").First().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.Where(p => p.Name == "SecureBootHack-V2-EFIESP").First().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.Where(p => p.Name == "SecureBootHack-V2-EFIESP").First().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.Where(p => p.Name == "SecureBootHack-V2-EFIESP").First().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() { if (IsSupportedFfuNeeded) { if (SupportedFFUPath == null) { IsSupportedFfuValid = true; // No visible warning when there is no SupportedFFU selected yet. } else { if (App.Config.FFURepository.Any(e => ((e.Path == SupportedFFUPath) && (App.PatchEngine.PatchDefinitions.Where(p => p.Name == "SecureBootHack-V2-EFIESP").First().TargetVersions.Any(v => v.Description == e.OSVersion))))) { IsSupportedFfuValid = true; } else { FFU SupportedFFU = new FFU(SupportedFFUPath); if (App.PatchEngine.PatchDefinitions.Where(p => p.Name == "SecureBootHack-V2-EFIESP").First().TargetVersions.Any(v => v.Description == SupportedFFU.GetOSVersion())) { IsSupportedFfuValid = true; } else { IsSupportedFfuValid = false; } } } } else { IsSupportedFfuValid = true; } if (IsSupportedFfuValid && (SupportedFFUPath != null)) ValidatedSupportedFfuPath = SupportedFFUPath; } private bool _IsSupportedFfuNeeded = false; public bool IsSupportedFfuNeeded { get { return _IsSupportedFfuNeeded; } set { _IsSupportedFfuNeeded = value; OnPropertyChanged("IsSupportedFfuNeeded"); OkCommand.RaiseCanExecuteChanged(); } } private bool _IsSupportedFfuValid = true; public bool IsSupportedFfuValid { get { return _IsSupportedFfuValid; } set { _IsSupportedFfuValid = value; OnPropertyChanged("IsSupportedFfuValid"); OkCommand.RaiseCanExecuteChanged(); } } private bool _IsProfileFfuValid = true; public bool IsProfileFfuValid { get { return _IsProfileFfuValid; } set { _IsProfileFfuValid = value; OnPropertyChanged("IsProfileFfuValid"); } } private bool _TargetHasNewFlashProtocol = false; public bool TargetHasNewFlashProtocol { get { return _TargetHasNewFlashProtocol; } set { _TargetHasNewFlashProtocol = value; OnPropertyChanged("TargetHasNewFlashProtocol"); OkCommand.RaiseCanExecuteChanged(); } } private bool _IsBootLoaderUnlocked = false; public bool IsBootLoaderUnlocked { get { return _IsBootLoaderUnlocked; } set { _IsBootLoaderUnlocked = value; OnPropertyChanged("IsBootLoaderUnlocked"); } } private string _FFUPath = null; public string FFUPath { get { return _FFUPath; } set { _FFUPath = value; OnPropertyChanged("FFUPath"); } } private string _ProfileFFUPath = null; public string ProfileFFUPath { get { return _ProfileFFUPath; } set { if (_ProfileFFUPath != value) { _ProfileFFUPath = value; OnPropertyChanged("ProfileFFUPath"); SetSupportedFFUPath(); OkCommand.RaiseCanExecuteChanged(); } } } private string _SupportedFFUPath = null; public string SupportedFFUPath { get { return _SupportedFFUPath; } set { if (_SupportedFFUPath != value) { _SupportedFFUPath = value; OnPropertyChanged("SupportedFFUPath"); if (value != ValidatedSupportedFfuPath) ValidateSupportedFfuPath(); } } } private string _LoadersPath = null; public string LoadersPath { get { return _LoadersPath; } set { _LoadersPath = value; OnPropertyChanged("LoadersPath"); } } private string _EDEPath = null; public string EDEPath { get { return _EDEPath; } set { _EDEPath = value; OnPropertyChanged("EDEPath"); } } private string _SBL3Path = null; public string SBL3Path { get { return _SBL3Path; } set { _SBL3Path = value; OnPropertyChanged("SBL3Path"); } } private string _CurrentMode = null; public string CurrentMode { get { return _CurrentMode; } set { _CurrentMode = value; OnPropertyChanged("CurrentMode"); } } private byte[] _RootKeyHash = null; public byte[] RootKeyHash { get { return _RootKeyHash; } set { _RootKeyHash = value; OnPropertyChanged("RootKeyHash"); } } private DelegateCommand _OkCommand = null; public DelegateCommand OkCommand { get { return _OkCommand; } private set { _OkCommand = value; } } private DelegateCommand _CancelCommand = null; public DelegateCommand CancelCommand { get { return _CancelCommand; } private set { _CancelCommand = value; } } private DelegateCommand _FixCommand = null; public DelegateCommand FixCommand { get { return _FixCommand; } private set { _FixCommand = value; } } } }