mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-14 03:16:40 +10:00
2661 lines
148 KiB
C#
2661 lines
148 KiB
C#
// 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 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 FFU(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.SecureFfuEnabled || Info.RdcPresent || Info.Authenticated, 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 PhoneNotifierViewModel();
|
|
UIContext.Send(s => Notifier.Start(), null);
|
|
NokiaFlashModel FlashModel = (NokiaFlashModel)(await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash));
|
|
|
|
List<FlashPart> Parts = new List<FlashPart>();
|
|
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 GPT(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();
|
|
UEFI_BS_NV.Name = "UEFI_BS_NV";
|
|
UEFI_BS_NV.Attributes = BACKUP_BS_NV.Attributes;
|
|
UEFI_BS_NV.PartitionGuid = OriginalPartitionGuid;
|
|
UEFI_BS_NV.PartitionTypeGuid = OriginalPartitionTypeGuid;
|
|
UEFI_BS_NV.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();
|
|
Part.StartSector = 0;
|
|
Part.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 LumiaV2UnlockBootViewModel.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<string> 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");
|
|
string Drive = Storage.Drive;
|
|
|
|
return 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 PhoneNotifierViewModel();
|
|
UIContext.Send(s => Notifier.Start(), null);
|
|
NokiaFlashModel FlashModel = (NokiaFlashModel)(await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash));
|
|
List<FlashPart> Parts = new List<FlashPart>();
|
|
|
|
// 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 GPT(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();
|
|
UEFI_BS_NV.Name = "UEFI_BS_NV";
|
|
UEFI_BS_NV.Attributes = BACKUP_BS_NV.Attributes;
|
|
UEFI_BS_NV.PartitionGuid = OriginalPartitionGuid;
|
|
UEFI_BS_NV.PartitionTypeGuid = OriginalPartitionTypeGuid;
|
|
UEFI_BS_NV.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 FlashPart();
|
|
Part.StartSector = 0;
|
|
Part.Stream = new MemoryStream(GPTChunk);
|
|
Parts.Add(Part);
|
|
}
|
|
|
|
using (MemoryStream Space = new MemoryStream(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 PhoneNotifierViewModel();
|
|
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 GPT(GPTChunk);
|
|
|
|
Partition TargetPartition = GPT.GetPartition(PartitionName);
|
|
if (TargetPartition == null)
|
|
throw new WPinternalsException("Target partition not found!");
|
|
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<FlashPart> Parts = new List<FlashPart>();
|
|
FlashPart Part;
|
|
if (string.Compare(PartitionName, "EFIESP", true) == 0)
|
|
{
|
|
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();
|
|
IsUnlockedFlag.Name = "IS_UNLOCKED";
|
|
IsUnlockedFlag.Attributes = 0;
|
|
IsUnlockedFlag.PartitionGuid = Guid.NewGuid();
|
|
IsUnlockedFlag.PartitionTypeGuid = Guid.NewGuid();
|
|
IsUnlockedFlag.FirstSector = 0x40;
|
|
IsUnlockedFlag.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();
|
|
Part.StartSector = 0;
|
|
Part.Stream = new MemoryStream(GPTChunk);
|
|
Parts.Add(Part);
|
|
}
|
|
}
|
|
|
|
using (FileStream Stream = new FileStream(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();
|
|
Part.StartSector = (UInt32)TargetPartition.FirstSector;
|
|
Part.Stream = Stream;
|
|
Parts.Add(Part);
|
|
await LumiaV2CustomFlash(Notifier, FFUPath, false, false, Parts, DoResetFirst, string.Compare(PartitionName, "UEFI_BS_NV", true) != 0);
|
|
}
|
|
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 PhoneNotifierViewModel();
|
|
UIContext.Send(s => Notifier.Start(), null);
|
|
|
|
NokiaFlashModel FlashModel = (NokiaFlashModel)(await SwitchModeViewModel.SwitchTo(Notifier, PhoneInterfaces.Lumia_Flash));
|
|
|
|
PhoneInfo Info = FlashModel.ReadPhoneInfo();
|
|
|
|
byte[] Data = System.IO.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 MemoryStream(Data))
|
|
{
|
|
FlashPart Part = new FlashPart() { StartSector = StartSector, Stream = Stream };
|
|
List<FlashPart> Parts = new List<FlashPart>();
|
|
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 FlashPart() { StartSector = StartSector, Stream = Data };
|
|
List<FlashPart> Parts = new List<FlashPart>();
|
|
Parts.Add(Part);
|
|
await LumiaV2CustomFlash(Notifier, FFUPath, PerformFullFlashFirst, SkipWrite, Parts, DoResetFirst, ClearFlashingStatusAtEnd, CheckSectorAlignment, ShowProgress, Experimental);
|
|
}
|
|
|
|
// Magic!
|
|
internal async static Task LumiaV2CustomFlash(PhoneNotifierViewModel Notifier, string FFUPath, bool PerformFullFlashFirst, bool SkipWrite, List<FlashPart> 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<FFUEntry> 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 FFU(FFUPath);
|
|
UInt32 UpdateType = ByteOperations.ReadUInt32(FFU.StoreHeader, 0);
|
|
if (UpdateType != 0)
|
|
throw new WPinternalsException("Only Full Flash images 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)
|
|
{
|
|
if ((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");
|
|
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");
|
|
UEFI UEFI = new UEFI(FFU.GetPartition("UEFI"));
|
|
string BootMgrName = UEFI.EFIs.Where(efi => ((efi.Name != null) && (efi.Name.Contains("BootMgrApp")))).First().Name;
|
|
UInt32 EstimatedSizeOfMemGap = (UInt32)UEFI.GetFile(BootMgrName).Length;
|
|
byte Options = 0;
|
|
if (SkipWrite)
|
|
Options = (byte)FlashOptions.SkipWrite;
|
|
if (!Info.SecureFfuEnabled || Info.Authenticated || Info.RdcPresent)
|
|
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<IDisposable>(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 QualcommSahara((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<IDisposable>(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");
|
|
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 = new FlashingPayload[0];
|
|
if (FlashParts != null)
|
|
{
|
|
payloads = GetNonOptimizedPayloads(FlashParts, FFU.ChunkSize, (uint)(Info.WriteBufferSize / FFU.ChunkSize), SetWorkingStatus, UpdateWorkingStatus).OrderBy(x => x.TargetLocations.Count()).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.Count();
|
|
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 (System.IO.FileStream FfuFile = new System.IO.FileStream(FFU.Path, System.IO.FileMode.Open, System.IO.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.Count(); i++)
|
|
{
|
|
UInt32 NewSecurityHeaderSize = SecurityHeaderSize + payloads[i].GetSecurityHeaderSize();
|
|
UInt32 NewStoreHeaderSize = StoreHeaderSize + payloads[i].GetStoreHeaderSize();
|
|
|
|
if (NewSecurityHeaderSize > HashSpace || NewStoreHeaderSize > DescriptorSpace)
|
|
{
|
|
HeadersFull = true;
|
|
break;
|
|
}
|
|
|
|
FlashingPhasePayloadCount += 1;
|
|
SecurityHeaderSize = NewSecurityHeaderSize;
|
|
StoreHeaderSize = NewStoreHeaderSize;
|
|
}
|
|
|
|
HashTableSize += SecurityHeaderSize;
|
|
WriteDescriptorCount += (UInt32)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.Count()); // 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.Count(), 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.Count() != 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.Count() != 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.Count() == 1 && !string.IsNullOrEmpty(flashPart.ProgressText))
|
|
NewProgressText = flashPart.ProgressText;
|
|
|
|
CurrentStream.Read(payloadBuffer, (Int32)(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.Count()) : 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<IDisposable>(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 QualcommSahara((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<IDisposable>(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 (System.IO.FileStream FfuFile = new System.IO.FileStream(FFU.Path, System.IO.FileMode.Open, System.IO.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<IDisposable>(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 QualcommSahara((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<IDisposable>(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.Count();
|
|
}
|
|
|
|
public UInt32 GetStoreHeaderSize()
|
|
{
|
|
return 0x08 * ((UInt32)TargetLocations.Count() + 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<FlashPart> 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<FlashingPayload> flashingPayloads = new List<FlashingPayload>();
|
|
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...", (UInt64)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<FlashPart> flashParts, Int32 chunkSize, UInt32 MaximumChunkCount, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null)
|
|
{
|
|
List<FlashingPayload> flashingPayloads = new List<FlashingPayload>();
|
|
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...", (UInt64)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.First(), hash)))
|
|
{
|
|
var payloadIndex = flashingPayloads.FindIndex(x => ByteOperations.Compare(x.ChunkHashes.First(), 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<EmergencyFileEntry> RKHEntries = App.Config.EmergencyRepository.Where(e => (StructuralComparisons.StructuralEqualityComparer.Equals(e.RKH, RKH) && e.ProgrammerExists()));
|
|
if (RKHEntries.Count() > 0)
|
|
{
|
|
if (RKHEntries.Count() == 1)
|
|
return RKHEntries.First().ProgrammerPath;
|
|
else
|
|
{
|
|
EmergencyFileEntry RKHEntry = RKHEntries.Where(e => string.Compare(e.Type, Type, false) == 0).FirstOrDefault();
|
|
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.Where(e => ((string.Compare(e.Type, Type, false) == 0) && e.ProgrammerExists())).FirstOrDefault();
|
|
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 GPT(GPTChunk);
|
|
|
|
Partition Target;
|
|
FlashPart Part;
|
|
List<FlashPart> Parts = new List<FlashPart>();
|
|
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;
|
|
|
|
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 FileStream(ArchivePath, FileMode.Open))
|
|
{
|
|
using (ZipArchive Archive = new ZipArchive(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 StreamReader(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.Where(p => string.Compare(p.Name, PartitionName, true) == 0).FirstOrDefault();
|
|
if (Partition != null)
|
|
{
|
|
using (DecompressedStream DecompressedStream = new DecompressedStream(Entry.Open()))
|
|
{
|
|
ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200;
|
|
try
|
|
{
|
|
StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200;
|
|
}
|
|
catch { }
|
|
|
|
TotalSizeSectors += StreamLengthInSectors;
|
|
PartitionCount++;
|
|
|
|
if (string.Compare(PartitionName, "MainOS", true) == 0)
|
|
{
|
|
MainOSOldSectorCount = Partition.SizeInSectors;
|
|
MainOSNewSectorCount = StreamLengthInSectors;
|
|
FirstMainOSSector = Partition.FirstSector;
|
|
}
|
|
else if (string.Compare(PartitionName, "Data", true) == 0)
|
|
{
|
|
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.Compare(PartitionName, "EFIESP", true) == 0)
|
|
{
|
|
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();
|
|
IsUnlockedFlag.Name = "IS_UNLOCKED";
|
|
IsUnlockedFlag.Attributes = 0;
|
|
IsUnlockedFlag.PartitionGuid = Guid.NewGuid();
|
|
IsUnlockedFlag.PartitionTypeGuid = Guid.NewGuid();
|
|
IsUnlockedFlag.FirstSector = 0x40;
|
|
IsUnlockedFlag.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 ((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.Where(p => string.Compare(p.Name, "MainOS", true) == 0).Single();
|
|
Partition DataPartition = GPT.Partitions.Where(p => string.Compare(p.Name, "Data", true) == 0).Single();
|
|
MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1;
|
|
DataPartition.FirstSector = MainOSPartition.LastSector + 1;
|
|
if ((DataPartition.FirstSector % 0x100) > 0)
|
|
DataPartition.FirstSector = ((UInt64)((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;
|
|
}
|
|
|
|
// 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();
|
|
UEFI_BS_NV.Name = "UEFI_BS_NV";
|
|
UEFI_BS_NV.Attributes = BACKUP_BS_NV.Attributes;
|
|
UEFI_BS_NV.PartitionGuid = OriginalPartitionGuid;
|
|
UEFI_BS_NV.PartitionTypeGuid = OriginalPartitionTypeGuid;
|
|
UEFI_BS_NV.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();
|
|
Part.StartSector = 0;
|
|
Part.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.Where(p => string.Compare(p.Name, PartitionName, true) == 0).FirstOrDefault();
|
|
if (Target != null)
|
|
{
|
|
Part = new FlashPart();
|
|
Part.StartSector = (UInt32)Target.FirstSector;
|
|
Part.Stream = new SeekableStream(() => new DecompressedStream(Entry.Open()), Entry.Length);
|
|
Part.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 != null) && p.ProgressText.StartsWith("Flashing partition "))).ToList().ForEach((p) =>
|
|
{
|
|
p.ProgressText += " (" + Count.ToString() + "/" + PartitionCount.ToString() + ")";
|
|
Count++;
|
|
});
|
|
|
|
// Do actual flashing!
|
|
await LumiaV2CustomFlash(Notifier, null, false, false, Parts, true, false, 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");
|
|
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 GPT(GPTChunk);
|
|
|
|
Partition Target;
|
|
FlashPart Part;
|
|
List<FlashPart> Parts = new List<FlashPart>();
|
|
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;
|
|
|
|
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();
|
|
IsUnlockedFlag.Name = "IS_UNLOCKED";
|
|
IsUnlockedFlag.Attributes = 0;
|
|
IsUnlockedFlag.PartitionGuid = Guid.NewGuid();
|
|
IsUnlockedFlag.PartitionTypeGuid = Guid.NewGuid();
|
|
IsUnlockedFlag.FirstSector = 0x40;
|
|
IsUnlockedFlag.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 (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.Where(p => string.Compare(p.Name, "MainOS", true) == 0).Single();
|
|
Partition DataPartition = GPT.Partitions.Where(p => string.Compare(p.Name, "Data", true) == 0).Single();
|
|
MainOSPartition.LastSector = MainOSPartition.FirstSector + MainOSNewSectorCount - 1;
|
|
DataPartition.FirstSector = MainOSPartition.LastSector + 1;
|
|
if ((DataPartition.FirstSector % 0x100) > 0)
|
|
DataPartition.FirstSector = ((UInt64)((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;
|
|
}
|
|
|
|
// 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();
|
|
UEFI_BS_NV.Name = "UEFI_BS_NV";
|
|
UEFI_BS_NV.Attributes = BACKUP_BS_NV.Attributes;
|
|
UEFI_BS_NV.PartitionGuid = OriginalPartitionGuid;
|
|
UEFI_BS_NV.PartitionTypeGuid = OriginalPartitionTypeGuid;
|
|
UEFI_BS_NV.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();
|
|
Part.StartSector = 0;
|
|
Part.Stream = new MemoryStream(GPTChunk);
|
|
Parts.Add(Part);
|
|
}
|
|
|
|
int Count = 0;
|
|
|
|
Target = GPT.Partitions.Where(p => string.Compare(p.Name, "EFIESP", true) == 0).FirstOrDefault();
|
|
if ((EFIESPPath != null) && (Target != null))
|
|
{
|
|
Count++;
|
|
Part = new FlashPart();
|
|
Part.StartSector = (UInt32)Target.FirstSector;
|
|
Part.Stream = new FileStream(EFIESPPath, FileMode.Open);
|
|
Part.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.Where(p => string.Compare(p.Name, "MainOS", true) == 0).FirstOrDefault();
|
|
if ((MainOSPath != null) && (Target != null))
|
|
{
|
|
Count++;
|
|
Part = new FlashPart();
|
|
Part.StartSector = (UInt32)Target.FirstSector;
|
|
Part.Stream = new FileStream(MainOSPath, FileMode.Open);
|
|
Part.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.Where(p => string.Compare(p.Name, "Data", true) == 0).FirstOrDefault();
|
|
if ((DataPath != null) && (Target != null))
|
|
{
|
|
Count++;
|
|
Part = new FlashPart();
|
|
Part.StartSector = (UInt32)Target.FirstSector;
|
|
Part.Stream = new FileStream(DataPath, FileMode.Open);
|
|
Part.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, false, 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 List<Allocation> Allocations = new List<Allocation>();
|
|
private static List<FreeMemRange> FreeMemRanges = new List<FreeMemRange>();
|
|
|
|
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();
|
|
NewAllocation.TotalStart = FreeMemRanges[i].End - TotalSize + 1;
|
|
|
|
if (FreeMemRanges[i].Size == TotalSize)
|
|
FreeMemRanges.RemoveAt(i);
|
|
else
|
|
FreeMemRanges[i].End -= TotalSize;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NewAllocation == null)
|
|
{
|
|
UInt32 FreeBuffer;
|
|
|
|
if (Allocations.Count() > 0)
|
|
FreeBuffer = Allocations[0].TotalStart;
|
|
else
|
|
FreeBuffer = 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();
|
|
NewAllocation.TotalStart = FreeMemRanges[i].End - TotalSize + 1;
|
|
|
|
if (FreeMemRanges[i].Size == TotalSize)
|
|
FreeMemRanges.RemoveAt(i);
|
|
else
|
|
FreeMemRanges[i].End -= TotalSize;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NewAllocation == null)
|
|
{
|
|
UInt32 FreeBuffer;
|
|
|
|
if (Allocations.Count() > 0)
|
|
FreeBuffer = Allocations[0].TotalStart;
|
|
else
|
|
FreeBuffer = 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 FreeMemRange();
|
|
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;
|
|
}
|
|
}
|